Merge pull request #585 from YunoHost/group_permission

Group permission
This commit is contained in:
Alexandre Aubin 2019-07-05 21:00:51 +02:00 committed by GitHub
commit ac7102a0ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 2232 additions and 207 deletions

View file

@ -58,7 +58,7 @@ _global:
# User #
#############################
user:
category_help: Manage users
category_help: Manage users and groups
actions:
### user_list()
@ -200,6 +200,172 @@ user:
help: Username or email to get information
subcategories:
group:
subcategory_help: Manage group
actions:
### user_group_list()
list:
action_help: List group
api: GET /users/groups
arguments:
--fields:
help: fields to fetch
nargs: "+"
### user_group_add()
add:
action_help: Create group
api: POST /users/groups
arguments:
groupname:
help: The unique group name to add
extra:
pattern: &pattern_groupname
- !!str ^[a-z0-9_]+$
- "pattern_groupname"
### user_group_delete()
delete:
action_help: Delete group
api: DELETE /users/groups/<groupname>
arguments:
groupname:
help: Username to delete
extra:
pattern: *pattern_groupname
### user_group_update()
update:
action_help: Update group
api: PUT /users/groups/<groupname>
arguments:
groupname:
help: Username to update
extra:
pattern: *pattern_groupname
-a:
full: --add-user
help: User to add in group
nargs: "*"
metavar: USERNAME
extra:
pattern: *pattern_username
-r:
full: --remove-user
help: User to remove in group
nargs: "*"
metavar: USERNAME
extra:
pattern: *pattern_username
### user_group_info()
info:
action_help: Get group information
api: GET /users/groups/<groupname>
arguments:
groupname:
help: Groupname to get information
extra:
pattern: *pattern_username
permission:
subcategory_help: Manage user permission
actions:
### user_permission_list()
list:
action_help: List access to user and group
api: GET /users/permission/<app>
arguments:
-a:
full: --app
help: Application to manage the permission
nargs: "*"
metavar: APP
-p:
full: --permission
help: Name of permission (main by default)
nargs: "*"
metavar: PERMISSION
-u:
full: --username
help: Username
nargs: "*"
metavar: USER
-g:
full: --group
help: Group name
nargs: "*"
metavar: GROUP
### user_permission_add()
add:
action_help: Grant access right to users and group
api: POST /users/permission/<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_remove()
remove:
action_help: Revoke access right to users and group
api: PUT /users/permission/<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/permission/<app>
arguments:
app:
help: Application to manage the permission
nargs: "+"
-p:
full: --permission
help: Name of permission (main by default)
nargs: "*"
metavar: PERMISSION
ssh:
subcategory_help: Manage ssh access
@ -1419,8 +1585,6 @@ tools:
postinstall:
action_help: YunoHost post-install
api: POST /postinstall
configuration:
authenticate: false
arguments:
-d:
full: --domain

View file

@ -227,3 +227,73 @@ ynh_webpath_register () {
sudo yunohost app register-url $app $domain $path_url
}
# Create a new permission for the app
#
# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--urls "url" ["url" ...]]
# | arg: app - the application id
# | arg: permission - the name for the permission (by default a permission named "main" already exist)
# | arg: defaultdisallow - define if all user will be allowed by default
# | arg: urls - the list of urls for the the permission
ynh_permission_create() {
declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=urls= )
local app
local permission
local defaultdisallow
local urls
ynh_handle_getopts_args "$@"
if [[ -n ${defaultdisallow:-} ]]; then
defaultdisallow=",default_allow=False"
fi
if [[ -n ${urls:-} ]]; then
urls=",urls=['${urls//';'/"','"}']"
fi
yunohost tools shell -c "from yunohost.permission import permission_add; permission_add('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)"
}
# Remove a permission for the app (note that when the app is removed all permission is automatically removed)
#
# usage: ynh_permission_remove --app "app" --permission "permission"
# | arg: app - the application id
# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
ynh_permission_remove() {
declare -Ar args_array=( [a]=app= [p]=permission= )
local app
local permission
ynh_handle_getopts_args "$@"
yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove('$app', '$permission', sync_perm=False)"
}
# Add a path managed by the SSO
#
# usage: ynh_permission_add_path --app "app" --permission "permission" --url "url" ["url" ...]
# | arg: app - the application id
# | arg: permission - the name for the permission
# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin)
ynh_permission_add_path() {
declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= )
local app
local permission
local url
ynh_handle_getopts_args "$@"
yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)"
}
# Remove a path managed by the SSO
#
# usage: ynh_permission_del_path --app "app" --permission "permission" --url "url" ["url" ...]
# | arg: app - the application id
# | arg: permission - the name for the permission
# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin)
ynh_permission_del_path() {
declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= )
local app
local permission
local url
ynh_handle_getopts_args "$@"
yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)"
}

View file

@ -60,7 +60,7 @@ do_pre_regen() {
# copy configuration files
cp -a ldap.conf slapd.conf "$ldap_dir"
cp -a sudo.schema mailserver.schema "$schema_dir"
cp -a sudo.schema mailserver.schema yunohost.schema "$schema_dir"
install -D -m 644 slapd.default "${pending_dir}/etc/default/slapd"
}

View file

@ -17,6 +17,12 @@ parents:
- organizationalUnit
- top
ou=permission:
ou: permission
objectClass:
- organizationalUnit
- top
ou=groups:
ou: groups
objectClass:
@ -29,22 +35,6 @@ parents:
- top
children:
cn=admins,ou=groups:
cn: admins
gidNumber: "4001"
memberUid: admin
objectClass:
- posixGroup
- top
cn=sftpusers,ou=groups:
cn: sftpusers
gidNumber: "4002"
memberUid: admin
objectClass:
- posixGroup
- top
cn=admin,ou=sudo:
cn: admin
sudoUser: admin
@ -54,3 +44,34 @@ children:
objectClass:
- sudoRole
- top
cn=admins,ou=groups:
cn: admins
gidNumber: "4001"
memberUid: admin
objectClass:
- posixGroup
- top
cn=all_users,ou=groups:
cn: all_users
gidNumber: "4002"
objectClass:
- posixGroup
- groupOfNamesYnh
depends_children:
cn=main.mail,ou=permission:
cn: main.mail
gidNumber: "5001"
objectClass:
- posixGroup
- permissionYnh
groupPermission:
- "cn=all_users,ou=groups,dc=yunohost,dc=org"
cn=main.metronome,ou=permission:
cn: main.metronome
gidNumber: "5002"
objectClass:
- posixGroup
- permissionYnh
groupPermission:
- "cn=all_users,ou=groups,dc=yunohost,dc=org"

View file

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

View file

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

View file

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

View file

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

View file

@ -14,6 +14,7 @@ include /etc/ldap/schema/nis.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/mailserver.schema
include /etc/ldap/schema/sudo.schema
include /etc/ldap/schema/yunohost.schema
# Where the pid file is put. The init.d script
# will not stop the server if you change this.
@ -117,3 +118,32 @@ access to *
by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write
by * read
# Configure Memberof Overlay (used for Yunohost permission)
# Link user <-> group
#dn: olcOverlay={0}memberof,olcDatabase={1}mdb,cn=config
overlay memberof
memberof-group-oc groupOfNamesYnh
memberof-member-ad member
memberof-memberof-ad memberOf
memberof-dangling error
memberof-refint TRUE
# Link permission <-> groupes
#dn: olcOverlay={1}memberof,olcDatabase={1}mdb,cn=config
overlay memberof
memberof-group-oc permissionYnh
memberof-member-ad groupPermission
memberof-memberof-ad permission
memberof-dangling error
memberof-refint TRUE
# Link permission <-> user
#dn: olcOverlay={2}memberof,olcDatabase={1}mdb,cn=config
overlay memberof
memberof-group-oc permissionYnh
memberof-member-ad inheritPermission
memberof-memberof-ad permission
memberof-dangling error
memberof-refint TRUE

View file

@ -0,0 +1,33 @@
#dn: cn=yunohost,cn=schema,cn=config
#objectClass: olcSchemaConfig
#cn: yunohost
# ATTRIBUTES
# For Permission
attributetype ( 1.3.6.1.4.1.17953.9.1.1 NAME 'permission'
DESC 'Yunohost permission on user and group side'
SUP distinguishedName )
attributetype ( 1.3.6.1.4.1.17953.9.1.2 NAME 'groupPermission'
DESC 'Yunohost permission for a group on permission side'
SUP distinguishedName )
attributetype ( 1.3.6.1.4.1.17953.9.1.3 NAME 'inheritPermission'
DESC 'Yunohost permission for user on permission side'
SUP distinguishedName )
attributetype ( 1.3.6.1.4.1.17953.9.1.4 NAME 'URL'
DESC 'Yunohost application URL'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{128} )
# OBJECTCLASS
# For Applications
objectclass ( 1.3.6.1.4.1.17953.9.2.1 NAME 'groupOfNamesYnh'
DESC 'Yunohost user group'
SUP top AUXILIARY
MAY ( member $ businessCategory $ seeAlso $ owner $ ou $ o $ permission ) )
objectclass ( 1.3.6.1.4.1.17953.9.2.2 NAME 'permissionYnh'
DESC 'a Yunohost application'
SUP top AUXILIARY
MUST cn
MAY ( groupPermission $ inheritPermission $ URL ) )
# For User
objectclass ( 1.3.6.1.4.1.17953.9.2.3 NAME 'userPermissionYnh'
DESC 'a Yunohost application'
SUP top AUXILIARY
MAY ( permission ) )

View file

@ -49,6 +49,8 @@
"app_upgrade_failed": "Unable to upgrade {app:s}",
"app_upgrade_some_app_failed": "Unable to upgrade some applications",
"app_upgraded": "{app:s} has been upgraded",
"apps_permission_not_found": "No permission found for the installed apps",
"apps_permission_restoration_failed": "Permission '{permission:s}' for app {app:s} restoration has failed",
"appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.",
"appslist_could_not_migrate": "Could not migrate app list {appslist:s}! Unable to parse the url… The old cron job has been kept in {bkp_file:s}.",
"appslist_fetched": "The application list {appslist:s} has been fetched",
@ -115,7 +117,8 @@
"backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders",
"backup_output_directory_not_empty": "The output directory is not empty",
"backup_output_directory_required": "You must provide an output directory for the backup",
"backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard drive or usb key.",
"backup_output_symlink_dir_broken": "You have a broken symlink instead of your archives directory '{path:s}'. You may have a specific setup to backup your data on an other filesystem, in this case you probably forgot to remount or plug your hard dirve or usb key.",
"backup_permission": "Backup permission for app {app:s}",
"backup_php5_to_php7_migration_may_fail": "Could not convert your archive to support php7, your php apps may fail to restore (reason: {error:s})",
"backup_running_hooks": "Running backup hooks…",
"backup_system_part_failed": "Unable to backup the '{part:s}' system part",
@ -193,6 +196,9 @@
"dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}",
"dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.",
"dyndns_unavailable": "Domain {domain:s} is not available.",
"edit_group_not_allowed": "You are not allowed to edit the group {group:s}",
"edit_permission_with_group_all_users_not_allowed": "You are not allowed to edit permission for group 'all_users', use 'yunohost user permission clear APP' or 'yunohost user permission add APP -u USER' instead.",
"error_when_removing_sftpuser_group": "Error when trying remove sftpusers group",
"executing_command": "Executing command '{command:s}'…",
"executing_script": "Executing script '{script:s}'…",
"extracting": "Extracting…",
@ -224,6 +230,18 @@
"global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.",
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
"good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
"group_already_allowed": "Group '{group:s}' already has permission '{permission:s}' enabled for app '{app:s}'",
"group_already_disallowed": "Group '{group:s}' already has permissions '{permission:s}' disabled for app '{app:s}'",
"group_name_already_exist": "Group {name:s} already exist",
"group_created": "Group '{group}' successfully created",
"group_creation_failed": "Group creation failed for group '{group}'",
"group_deleted": "Group '{group}' deleted",
"group_deletion_failed": "Group '{group} 'deletion failed",
"group_deletion_not_allowed": "The group {group:s} cannot be deleted manually.",
"group_info_failed": "Group info failed",
"group_unknown": "Group {group:s} unknown",
"group_updated": "Group '{group}' updated",
"group_update_failed": "Group update failed for group '{group}'",
"hook_exec_failed": "Script execution failed: {path:s}",
"hook_exec_not_terminated": "Script execution did not finish properly: {path:s}",
"hook_json_return_error": "Failed to read return from hook {path:s}. Error: {msg:s}. Raw content: {raw_content}",
@ -262,13 +280,21 @@
"log_dyndns_subscribe": "Subscribe to a YunoHost subdomain '{}'",
"log_dyndns_update": "Update the ip associated with your YunoHost subdomain '{}'",
"log_letsencrypt_cert_install": "Install Let's encrypt certificate on '{}' domain",
"log_permission_add": "Add permission '{}' for app '{}'",
"log_permission_remove": "Remove permission '{}'",
"log_permission_update": "Update permission '{}' for app '{}'",
"log_selfsigned_cert_install": "Install self signed certificate on '{}' domain",
"log_letsencrypt_cert_renew": "Renew '{}' Let's encrypt certificate",
"log_service_enable": "Enable '{}' service",
"log_regen_conf": "Regenerate system configurations '{}'",
"log_user_create": "Add '{}' user",
"log_user_delete": "Delete '{}' user",
"log_user_group_add": "Add '{}' group",
"log_user_group_delete": "Delete '{}' group",
"log_user_group_update": "Update '{}' group",
"log_user_update": "Update information of '{}' user",
"log_user_permission_add": "Update '{}' permission",
"log_user_permission_remove": "Update '{}' permission",
"log_tools_maindomain": "Make '{}' as main domain",
"log_tools_migrations_migrate_forward": "Migrate forward",
"log_tools_migrations_migrate_backward": "Migrate backward",
@ -282,6 +308,7 @@
"mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'",
"mail_domain_unknown": "Unknown mail address domain '{domain:s}'",
"mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'",
"mailbox_disabled": "Mailbox disabled for user {user:s}",
"mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space",
"mail_unavailable": "This email address is reserved and shall be automatically allocated to the very first user",
"maindomain_change_failed": "Unable to change the main domain",
@ -304,6 +331,7 @@
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)",
"migration_description_0009_decouple_regenconf_from_services": "Decouple the regen-conf mechanism from services",
"migration_description_0010_migrate_to_apps_json": "Remove deprecated appslists and use the new unified 'apps.json' list instead",
"migration_description_0011_setup_group_permission": "Setup user group and setup permission for apps and services",
"migration_0003_backward_impossible": "The stretch migration cannot be reverted.",
"migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.",
"migration_0003_patching_sources_list": "Patching the sources.lists…",
@ -330,6 +358,17 @@
"migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.",
"migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;)! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.",
"migration_0009_not_needed": "This migration already happened somehow ? Skipping.",
"migration_0011_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.",
"migration_0011_can_not_backup_before_migration": "The backup of the system before the migration failed. Migration failed. Error: {error:s}",
"migration_0011_create_group": "Creating a group for each user...",
"migration_0011_done": "Migration successful. You are now able to manage groups of users.",
"migration_0011_LDAP_config_dirty": "It look like that you customized your LDAP configuration. For this migration the LDAP configuration need to be updated.\nYou need to save your actual configuration, reintialize the original configuration by the command 'yunohost tools regen-conf -f' and after retry the migration",
"migration_0011_LDAP_update_failed": "LDAP update failed. Error: {error:s}",
"migration_0011_migrate_permission": "Migrating permissions from apps settings to LDAP...",
"migration_0011_migration_failed_trying_to_rollback": "Migration failed ... trying to rollback the system.",
"migration_0011_rollback_success": "Rollback succeeded.",
"migration_0011_update_LDAP_database": "Updating LDAP database...",
"migration_0011_update_LDAP_schema": "Updating LDAP schema...",
"migrations_backward": "Migrating backward.",
"migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}",
"migrations_cant_reach_migration_file": "Can't access migrations files at path %s",
@ -358,6 +397,7 @@
"mysql_db_creation_failed": "MySQL database creation failed",
"mysql_db_init_failed": "MySQL database init failed",
"mysql_db_initialized": "The MySQL database has been initialized",
"need_define_permission_before": "You need to redefine the permission using 'yunohost user permission add -u USER' before removing an allowed group",
"network_check_mx_ko": "DNS MX record is not set",
"network_check_smtp_ko": "Outbound mail (SMTP port 25) seems to be blocked by your network",
"network_check_smtp_ok": "Outbound mail (SMTP port 25) is not blocked",
@ -391,11 +431,25 @@
"pattern_positive_number": "Must be a positive number",
"pattern_username": "Must be lower-case alphanumeric and underscore characters only",
"pattern_password_app": "Sorry, passwords should not contain the following characters: {forbidden_chars}",
"permission_already_clear": "Permission '{permission:s}' already clear for app {app:s}",
"permission_already_exist": "Permission '{permission:s}' for app {app:s} already exist",
"permission_created": "Permission '{permission:s}' for app {app:s} created",
"permission_creation_failed": "Permission creation failed",
"permission_deleted": "Permission '{permission:s}' for app {app:s} deleted",
"permission_deletion_failed": "Permission '{permission:s}' for app {app:s} deletion failed",
"permission_not_found": "Permission '{permission:s}' not found for application {app:s}",
"permission_name_not_valid": "Permission name '{permission:s}' not valid",
"permission_update_failed": "Permission update failed",
"permission_generated": "The permission database has been updated",
"permission_updated": "Permission '{permission:s}' for app {app:s} updated",
"permission_update_nothing_to_do": "No permissions to update",
"port_already_closed": "Port {port:d} is already closed for {ip_version:s} connections",
"port_already_opened": "Port {port:d} is already opened for {ip_version:s} connections",
"port_available": "Port {port:d} is available",
"port_unavailable": "Port {port:d} is not available",
"recommend_to_add_first_user": "The post-install is finished but YunoHost needs at least one user to work correctly, you should add one using 'yunohost user create $username' or the admin interface.",
"remove_main_permission_not_allowed": "Removing the main permission is not allowed",
"remove_user_of_group_not_allowed": "You are not allowed to remove the user {user:s} in the group {group:s}",
"regenconf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'",
"regenconf_file_copy_failed": "Unable to 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 has been kept back.",
@ -481,6 +535,7 @@
"ssowat_conf_updated": "The SSOwat configuration has been updated",
"ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax",
"ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax",
"system_groupname_exists": "Groupname already exists in the system group",
"system_upgraded": "The system has been upgraded",
"system_username_exists": "Username already exists in the system users",
"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`.",
@ -508,12 +563,14 @@
"upnp_disabled": "UPnP has been disabled",
"upnp_enabled": "UPnP has been enabled",
"upnp_port_open_failed": "Unable to open UPnP ports",
"user_already_in_group": "User {user:} already in group {group:s}",
"user_created": "The user has been created",
"user_creation_failed": "Unable to create user",
"user_deleted": "The user has been deleted",
"user_deletion_failed": "Unable to delete user",
"user_home_creation_failed": "Unable to create user home folder",
"user_info_failed": "Unable to retrieve user information",
"user_not_in_group": "User {user:s} not in group {group:s}",
"user_unknown": "Unknown user: {user:s}",
"user_update_failed": "Unable to update user",
"user_updated": "The user has been updated",

View file

@ -400,6 +400,9 @@ def app_map(app=None, raw=False, user=None):
app -- Specific app to map
"""
from yunohost.permission import user_permission_list
from yunohost.utils.ldap import _get_ldap_interface
apps = []
result = {}
@ -419,11 +422,10 @@ def app_map(app=None, raw=False, user=None):
if 'no_sso' in app_settings: # I don't think we need to check for the value here
continue
if user is not None:
if ('mode' not in app_settings
or ('mode' in app_settings
and app_settings['mode'] == 'private')) \
and 'allowed_users' in app_settings \
and user not in app_settings['allowed_users'].split(','):
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
domain = app_settings['domain']
@ -455,6 +457,7 @@ def app_change_url(operation_logger, app, domain, path):
"""
from yunohost.hook import hook_exec, hook_callback
from yunohost.domain import _normalize_domain_path, _get_conflicting_apps
from yunohost.permission import permission_update
installed = _is_installed(app)
if not installed:
@ -544,10 +547,10 @@ def app_change_url(operation_logger, app, domain, path):
app_setting(app, 'domain', value=domain)
app_setting(app, 'path', value=path)
app_ssowatconf()
permission_update(app, permission="main", add_url=[domain+path], remove_url=[old_domain+old_path], sync_perm=True)
# avoid common mistakes
if _run_service_command("reload", "nginx") == False:
if _run_service_command("reload", "nginx") is False:
# grab nginx errors
# the "exit 0" is here to avoid check_output to fail because 'nginx -t'
# will return != 0 since we are in a failed state
@ -577,6 +580,7 @@ def app_upgrade(app=[], url=None, file=None):
raise YunohostError("dpkg_is_broken")
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
from yunohost.permission import permission_sync_to_user
# Retrieve interface
is_api = msettings.get('interface') == 'api'
@ -699,7 +703,7 @@ def app_upgrade(app=[], url=None, file=None):
if not_upgraded_apps:
raise YunohostError('app_not_upgraded', apps=', '.join(not_upgraded_apps))
app_ssowatconf()
permission_sync_to_user()
logger.success(m18n.n('upgrade_complete'))
@ -719,8 +723,11 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
if packages.dpkg_is_broken():
raise YunohostError("dpkg_is_broken")
from yunohost.utils.ldap import _get_ldap_interface
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
from yunohost.log import OperationLogger
from yunohost.permission import permission_add, permission_update, permission_remove, permission_sync_to_user
ldap = _get_ldap_interface()
# Fetch or extract sources
try:
@ -848,6 +855,11 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
if os.path.exists(os.path.join(extracted_app_folder, file_to_copy)):
os.system('cp -R %s/%s %s' % (extracted_app_folder, file_to_copy, app_setting_path))
# Create permission before the install (useful if the install script redefine the permission)
# Note that sync_perm is disabled to avoid triggering a whole bunch of code and messages
# can't be sure that we don't have one case when it's needed
permission_add(app=app_instance_name, permission="main", sync_perm=False)
# Execute the app install script
install_retcode = 1
try:
@ -880,6 +892,13 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
os.path.join(extracted_app_folder, 'scripts/remove'),
args=[app_instance_name], env=env_dict_remove
)[0]
# Remove all permission in LDAP
result = ldap.search(base='ou=permission,dc=yunohost,dc=org',
filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn'])
permission_list = [p['cn'][0] for p in result]
for l in permission_list:
permission_remove(app_instance_name, l.split('.')[0], force=True)
if remove_retcode != 0:
msg = m18n.n('app_not_properly_removed',
app=app_instance_name)
@ -919,7 +938,14 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
os.system('chown -R root: %s' % app_setting_path)
os.system('chown -R admin: %s/scripts' % app_setting_path)
app_ssowatconf()
# Add path in permission if it's defined in the app install script
app_settings = _get_app_settings(app_instance_name)
domain = app_settings.get('domain', None)
path = app_settings.get('path', None)
if domain and path:
permission_update(app_instance_name, permission="main", add_url=[domain+path], sync_perm=False)
permission_sync_to_user()
logger.success(m18n.n('installation_complete'))
@ -935,7 +961,9 @@ def app_remove(operation_logger, app):
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.permission import permission_remove, permission_sync_to_user
if not _is_installed(app):
raise YunohostError('app_not_installed', app=app)
@ -979,13 +1007,23 @@ def app_remove(operation_logger, app):
shutil.rmtree(app_setting_path)
shutil.rmtree('/tmp/yunohost_remove')
hook_remove(app)
app_ssowatconf()
# Remove all permission in LDAP
ldap = _get_ldap_interface()
result = ldap.search(base='ou=permission,dc=yunohost,dc=org',
filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app, attrs=['cn'])
permission_list = [p['cn'][0] for p in result]
for l in permission_list:
permission_remove(app, l.split('.')[0], force=True, sync_perm=False)
permission_sync_to_user()
if packages.dpkg_is_broken():
raise YunohostError("this_action_broke_dpkg")
def app_addaccess(apps, users=[]):
@is_unit_operation(['permission','app'])
def app_addaccess(operation_logger, apps, users=[]):
"""
Grant access right to users (everyone by default)
@ -994,64 +1032,17 @@ def app_addaccess(apps, users=[]):
apps
"""
from yunohost.user import user_list, user_info
from yunohost.hook import hook_callback
from yunohost.permission import user_permission_update
result = {}
permission = user_permission_update(operation_logger, app=apps, permission="main", add_username=users)
if not users:
users = user_list()['users'].keys()
elif not isinstance(users, list):
users = [users, ]
if not isinstance(apps, list):
apps = [apps, ]
for app in apps:
app_settings = _get_app_settings(app)
if not app_settings:
continue
if 'mode' not in app_settings:
app_setting(app, 'mode', 'private')
app_settings['mode'] = 'private'
if app_settings['mode'] == 'private':
# Start register change on system
related_to = [('app', app)]
operation_logger = OperationLogger('app_addaccess', related_to)
operation_logger.start()
allowed_users = set()
if 'allowed_users' in app_settings and app_settings['allowed_users']:
allowed_users = set(app_settings['allowed_users'].split(','))
for allowed_user in users:
if allowed_user not in allowed_users:
try:
user_info(allowed_user)
except YunohostError:
logger.warning(m18n.n('user_unknown', user=allowed_user))
continue
allowed_users.add(allowed_user)
operation_logger.related_to.append(('user', allowed_user))
operation_logger.flush()
new_users = ','.join(allowed_users)
app_setting(app, 'allowed_users', new_users)
hook_callback('post_app_addaccess', args=[app, new_users])
operation_logger.success()
result[app] = allowed_users
app_ssowatconf()
result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()}
return {'allowed_users': result}
def app_removeaccess(apps, users=[]):
@is_unit_operation(['permission','app'])
def app_removeaccess(operation_logger, apps, users=[]):
"""
Revoke access right to users (everyone by default)
@ -1060,59 +1051,17 @@ def app_removeaccess(apps, users=[]):
apps
"""
from yunohost.user import user_list
from yunohost.hook import hook_callback
from yunohost.permission import user_permission_update
result = {}
permission = user_permission_update(operation_logger, app=apps, permission="main", del_username=users)
remove_all = False
if not users:
remove_all = True
elif not isinstance(users, list):
users = [users, ]
if not isinstance(apps, list):
apps = [apps, ]
for app in apps:
app_settings = _get_app_settings(app)
if not app_settings:
continue
allowed_users = set()
if app_settings.get('skipped_uris', '') != '/':
# Start register change on system
related_to = [('app', app)]
operation_logger = OperationLogger('app_removeaccess', related_to)
operation_logger.start()
if remove_all:
pass
elif 'allowed_users' in app_settings:
for allowed_user in app_settings['allowed_users'].split(','):
if allowed_user not in users:
allowed_users.add(allowed_user)
else:
for allowed_user in user_list()['users'].keys():
if allowed_user not in users:
allowed_users.add(allowed_user)
operation_logger.related_to += [('user', x) for x in allowed_users]
operation_logger.flush()
new_users = ','.join(allowed_users)
app_setting(app, 'allowed_users', new_users)
hook_callback('post_app_removeaccess', args=[app, new_users])
result[app] = allowed_users
operation_logger.success()
app_ssowatconf()
result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()}
return {'allowed_users': result}
def app_clearaccess(apps):
@is_unit_operation(['permission','app'])
def app_clearaccess(operation_logger, apps):
"""
Reset access rights for the app
@ -1120,33 +1069,13 @@ def app_clearaccess(apps):
apps
"""
from yunohost.hook import hook_callback
from yunohost.permission import user_permission_clear
if not isinstance(apps, list):
apps = [apps]
permission = user_permission_clear(operation_logger, app=apps, permission="main")
for app in apps:
app_settings = _get_app_settings(app)
if not app_settings:
continue
# Start register change on system
related_to = [('app', app)]
operation_logger = OperationLogger('app_clearaccess', related_to)
operation_logger.start()
if 'mode' in app_settings:
app_setting(app, 'mode', delete=True)
if 'allowed_users' in app_settings:
app_setting(app, 'allowed_users', delete=True)
hook_callback('post_app_clearaccess', args=[app])
operation_logger.success()
app_ssowatconf()
result = {p : v['main']['allowed_users'] for p, v in permission['permissions'].items()}
return {'allowed_users': result}
def app_debug(app):
"""
@ -1195,7 +1124,8 @@ def app_makedefault(operation_logger, app, domain=None):
operation_logger.start()
if '/' in app_map(raw=True)[domain]:
raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, other_app=app_map(raw=True)[domain]["/"]["id"])
raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain,
other_app=app_map(raw=True)[domain]["/"]["id"])
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
@ -1407,6 +1337,7 @@ def app_ssowatconf():
"""
from yunohost.domain import domain_list, _get_maindomain
from yunohost.user import user_list
from yunohost.permission import user_permission_list
main_domain = _get_maindomain()
domains = domain_list()['domains']
@ -1467,6 +1398,13 @@ def app_ssowatconf():
skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$")
skipped_regex.append("^[^/]*/%.well%-known/autoconfig/mail/config%-v1%.1%.xml.*$")
permission = {}
for a in user_permission_list()['permissions'].values():
for p in a.values():
if 'URL' in p:
for u in p['URL']:
permission[u] = p['allowed_users']
conf_dict = {
'portal_domain': main_domain,
'portal_path': '/yunohost/sso/',
@ -1487,12 +1425,13 @@ def app_ssowatconf():
'redirected_regex': redirected_regex,
'users': {username: app_map(user=username)
for username in user_list()['users'].keys()},
'permission': permission,
}
with open('/etc/ssowat/conf.json', 'w+') as f:
json.dump(conf_dict, f, sort_keys=True, indent=4)
logger.success(m18n.n('ssowat_conf_generated'))
logger.debug(m18n.n('ssowat_conf_generated'))
def app_change_label(app, new_label):

View file

@ -701,6 +701,12 @@ class BackupManager():
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)[0]
self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"])
# backup permissions
logger.debug(m18n.n('backup_permission', app=app))
ldap_url = "ldap:///dc=yunohost,dc=org???(&(objectClass=permissionYnh)(cn=*.%s))" % app
os.system("slapcat -b dc=yunohost,dc=org -H '%s' -l '%s/permission.ldif'" % (ldap_url, settings_dir))
except:
abs_tmp_app_dir = os.path.join(self.work_dir, 'apps/', app)
shutil.rmtree(abs_tmp_app_dir, ignore_errors=True)
@ -903,18 +909,17 @@ class RestoreManager():
logger.debug("executing the post-install...")
tools_postinstall(domain, 'Yunohost', True)
def clean(self):
"""
End a restore operations by cleaning the working directory and
regenerate ssowat conf (if some apps were restored)
"""
from permission import permission_sync_to_user
successfull_apps = self.targets.list("apps", include=["Success", "Warning"])
if successfull_apps != []:
# Quickfix: the old app_ssowatconf(auth) instruction failed due to
# ldap restore hooks
os.system('sudo yunohost app ssowatconf')
permission_sync_to_user(force=False)
if os.path.ismount(self.work_dir):
ret = subprocess.call(["umount", self.work_dir])
@ -1178,6 +1183,19 @@ class RestoreManager():
if system_targets == []:
return
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
# Backup old permission for apps
# We need to do that because in case of an app is installed we can't remove the permission for this app
old_apps_permission = []
try:
old_apps_permission = 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
operation_logger = OperationLogger('backup_restore_system')
operation_logger.start()
@ -1214,12 +1232,44 @@ class RestoreManager():
regen_conf()
# Check if we need to do the migration 0009 : setup group and permission
# Legacy code
result = ldap.search('ou=groups,dc=yunohost,dc=org',
'(&(objectclass=groupOfNamesYnh)(cn=all_users))',
['cn'])
if not result:
from yunohost.tools import _get_migration_by_name
setup_group_permission = _get_migration_by_name("setup_group_permission")
# Update LDAP schema restart slapd
logger.info(m18n.n("migration_0011_update_LDAP_schema"))
regen_conf(names=['slapd'], force=True)
setup_group_permission.migrate_LDAP_db()
# Remove all permission for all app which sill in the LDAP
for per in ldap.search('ou=permission,dc=yunohost,dc=org',
'(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))',
['cn']):
if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]):
raise YunohostError('permission_deletion_failed', permission=permission, app=app)
# Restore permission for the app which is installed
for per in old_apps_permission:
try:
permission_name, app_name = per['cn'][0].split('.')
except:
logger.warning(m18n.n('permission_name_not_valid', permission=per['cn'][0]))
if _is_installed(app_name):
if not ldap.add('cn=%s,ou=permission' % per['cn'][0], per):
raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name)
def _restore_apps(self):
"""Restore all apps targeted"""
apps_targets = self.targets.list("apps", exclude=["Skipped"])
for app in apps_targets:
print(app)
self._restore_app(app)
def _restore_app(self, app_instance_name):
@ -1249,6 +1299,12 @@ class RestoreManager():
name already exists
restore_app_failed -- Raised if the restore bash script failed
"""
from moulinette.utils.filesystem import read_ldif
from yunohost.user import user_group_list
from yunohost.permission import permission_remove
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
def copytree(src, dst, symlinks=False, ignore=None):
for item in os.listdir(src):
s = os.path.join(src, item)
@ -1311,6 +1367,26 @@ class RestoreManager():
filesystem.chown(tmp_folder_for_app_restore, 'admin', None, True)
restore_script = os.path.join(tmp_folder_for_app_restore, 'restore')
# Restore permissions
if os.path.isfile(app_settings_in_archive + '/permission.ldif'):
filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass',
'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid']
entries = read_ldif('%s/permission.ldif' % app_settings_in_archive, filtred_entries)
group_list = user_group_list(['cn'])['groups']
for dn, entry in entries:
# Remove the group which has been removed
for group in entry['groupPermission']:
group_name = group.split(',')[0].split('=')[1]
if group_name not in group_list:
entry['groupPermission'].remove(group)
print(entry)
if not ldap.add('cn=%s,ou=permission' % entry['cn'][0], entry):
raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name)
else:
from yunohost.tools import _get_migration_by_name
setup_group_permission = _get_migration_by_name("setup_group_permission")
setup_group_permission.migrate_app_permission(app=app_instance_name)
# Prepare env. var. to pass to script
env_dict = self._get_env_var(app_instance_name)
@ -1357,6 +1433,13 @@ class RestoreManager():
# Cleaning app directory
shutil.rmtree(app_settings_new_path, ignore_errors=True)
# Remove all permission in LDAP
result = ldap.search(base='ou=permission,dc=yunohost,dc=org',
filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn'])
permission_list = [p['cn'][0] for p in result]
for l in permission_list:
permission_remove(app_instance_name, l.split('.')[0], force=True)
# TODO Cleaning app hooks
else:
self.targets.set_result("apps", app_instance_name, "Success")

View file

@ -0,0 +1,142 @@
import yaml
import time
import os
from moulinette import m18n
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from yunohost.tools import Migration
from yunohost.user import user_group_add, user_group_update
from yunohost.app import app_setting, app_list
from yunohost.regenconf import regen_conf
from yunohost.permission import permission_add, permission_sync_to_user
from yunohost.user import user_permission_add
logger = getActionLogger('yunohost.migration')
###################################################
# Tools used also for restoration
###################################################
class MyMigration(Migration):
"""
Update the LDAP DB to be able to store the permission
Create a group for each yunohost user
Migrate app permission from apps setting to LDAP
"""
required = True
def migrate_LDAP_db(self):
logger.info(m18n.n("migration_0011_update_LDAP_database"))
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
try:
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:
attr_dict = ldap_map['parents']['ou=permission']
ldap.add('ou=permission', attr_dict)
attr_dict = ldap_map['children']['cn=all_users,ou=groups']
ldap.add('cn=all_users,ou=groups', attr_dict)
for rdn, attr_dict in ldap_map['depends_children'].items():
ldap.add(rdn, attr_dict)
except Exception as e:
raise YunohostError("migration_0011_LDAP_update_failed", error=e)
logger.info(m18n.n("migration_0011_create_group"))
# Create a group for each yunohost user
user_list = ldap.search('ou=users,dc=yunohost,dc=org',
'(&(objectclass=person)(!(uid=root))(!(uid=nobody)))',
['uid', 'uidNumber'])
for user_info in user_list:
username = user_info['uid'][0]
ldap.update('uid=%s,ou=users' % username,
{'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']})
user_group_add(username, gid=user_info['uidNumber'][0], sync_perm=False)
user_group_update(groupname=username, add_user=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):
logger.info(m18n.n("migration_0011_migrate_permission"))
if app:
apps = app_list(installed=True, filter=app)['apps']
else:
apps = app_list(installed=True)['apps']
for app_info in apps:
app = app_info['id']
permission = app_setting(app, 'allowed_users')
path = app_setting(app, 'path')
domain = app_setting(app, 'domain')
urls = [domain + path] if domain and path else None
permission_add(app, permission='main', urls=urls, default_allow=True, sync_perm=False)
if permission:
allowed_group = permission.split(',')
user_permission_add([app], permission='main', group=allowed_group, sync_perm=False)
app_setting(app, 'allowed_users', delete=True)
def migrate(self):
# Check if the migration can be processed
ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True)
# By this we check if the have been customized
if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']:
raise YunohostError("migration_0011_LDAP_config_dirty")
# Backup LDAP and the apps settings before to do the migration
logger.info(m18n.n("migration_0011_backup_before_migration"))
try:
backup_folder = "/home/yunohost.backup/premigration/" + time.strftime('%Y%m%d-%H%M%S', time.gmtime())
os.makedirs(backup_folder, 0o750)
os.system("systemctl stop slapd")
os.system("cp -r --preserve /etc/ldap %s/ldap_config" % backup_folder)
os.system("cp -r --preserve /var/lib/ldap %s/ldap_db" % backup_folder)
os.system("cp -r --preserve /etc/yunohost/apps %s/apps_settings" % backup_folder)
except Exception as e:
raise YunohostError("migration_0011_can_not_backup_before_migration", error=e)
finally:
os.system("systemctl start slapd")
try:
# Update LDAP schema restart slapd
logger.info(m18n.n("migration_0011_update_LDAP_schema"))
regen_conf(names=['slapd'], force=True)
# Update LDAP database
self.migrate_LDAP_db()
# Migrate permission
self.migrate_app_permission()
permission_sync_to_user()
except Exception as e:
logger.warn(m18n.n("migration_0011_migration_failed_trying_to_rollback"))
os.system("systemctl stop slapd")
os.system("rm -r /etc/ldap/slapd.d") # To be sure that we don't keep some part of the old config
os.system("cp -r --preserve %s/ldap_config/. /etc/ldap/" % backup_folder)
os.system("cp -r --preserve %s/ldap_db/. /var/lib/ldap/" % backup_folder)
os.system("cp -r --preserve %s/apps_settings/. /etc/yunohost/apps/" % backup_folder)
os.system("systemctl start slapd")
os.system("rm -r " + backup_folder)
logger.info(m18n.n("migration_0011_rollback_success"))
raise
else:
os.system("rm -r " + backup_folder)
logger.info(m18n.n("migration_0011_done"))

527
src/yunohost/permission.py Normal file
View file

@ -0,0 +1,527 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2014 YUNOHOST.ORG
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses
"""
""" yunohost_permission.py
Manage permissions
"""
import grp
import random
from moulinette import m18n
from moulinette.utils.log import getActionLogger
from yunohost.utils.error import YunohostError
from yunohost.user import user_list
from yunohost.log import is_unit_operation
logger = getActionLogger('yunohost.user')
def user_permission_list(app=None, permission=None, username=None, group=None):
"""
List permission for specific application
Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail
permission -- name of the permission ("main" by default)
username -- Username to get informations
group -- Groupname to get informations
"""
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
permission_attrs = [
'cn',
'groupPermission',
'inheritPermission',
'URL',
]
# Normally app is alway defined but it should be possible to set it
if app and not isinstance(app, list):
app = [app]
if permission and not isinstance(permission, list):
permission = [permission]
if not isinstance(username, list):
username = [username]
if not isinstance(group, list):
group = [group]
permissions = {}
result = ldap.search('ou=permission,dc=yunohost,dc=org',
'(objectclass=permissionYnh)', permission_attrs)
for res in result:
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
if app_name not in permissions:
permissions[app_name] = {}
permissions[app_name][permission_name] = {'allowed_users': [], 'allowed_groups': []}
for g in group_name:
permissions[app_name][permission_name]['allowed_groups'].append(g)
for u in user_name:
permissions[app_name][permission_name]['allowed_users'].append(u)
if 'URL' in res:
permissions[app_name][permission_name]['URL'] = []
for u in res['URL']:
permissions[app_name][permission_name]['URL'].append(u)
return {'permissions': permissions}
def user_permission_update(operation_logger, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None, sync_perm=True):
"""
Allow or Disallow a user or group to a permission for a specific application
Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail
permission -- name of the permission ("main" by default)
add_username -- Username to allow
add_group -- Groupname to allow
del_username -- Username to disallow
del_group -- Groupname to disallow
"""
from yunohost.hook import hook_callback
from yunohost.user import user_group_list
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
if permission:
if not isinstance(permission, list):
permission = [permission]
else:
permission = ["main"]
if add_group:
if not isinstance(add_group, list):
add_group = [add_group]
else:
add_group = []
if add_username:
if not isinstance(add_username, list):
add_username = [add_username]
else:
add_username = []
if del_group:
if not isinstance(del_group, list):
del_group = [del_group]
else:
del_group = []
if del_username:
if not isinstance(del_username, list):
del_username = [del_username]
else:
del_username = []
# Validate that the group exist
for g in add_group:
if g not in user_group_list(['cn'])['groups']:
raise YunohostError('group_unknown', group=g)
for u in add_username:
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)
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()
for per, val in new_per_dict.items():
# Don't update LDAP if we update exactly the same values
if val == set(result[per]['groupPermission'] if 'groupPermission' in result[per] else []):
continue
if ldap.update('cn=%s,ou=permission' % per, {'groupPermission': val}):
p = per.split('.')
logger.debug(m18n.n('permission_updated', permission=p[0], app=p[1]))
else:
raise YunohostError('permission_update_failed')
if sync_perm:
permission_sync_to_user()
for a in app:
allowed_users = set()
disallowed_users = set()
group_list = user_group_list(['member'])['groups']
for g in add_group:
if 'members' in group_list[g]:
allowed_users.union(group_list[g]['members'])
for g in del_group:
if 'members' in group_list[g]:
disallowed_users.union(group_list[g]['members'])
allowed_users = ','.join(allowed_users)
disallowed_users = ','.join(disallowed_users)
if add_group:
hook_callback('post_app_addaccess', args=[app, allowed_users])
if del_group:
hook_callback('post_app_removeaccess', args=[app, disallowed_users])
return user_permission_list(app, permission)
def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True):
"""
Reset the permission for a specific application
Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail
permission -- name of the permission ("main" by default)
username -- Username to get informations (all by default)
group -- Groupname to get informations (all by default)
"""
from yunohost.hook import hook_callback
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
if permission:
if not isinstance(permission, list):
permission = [permission]
else:
permission = ["main"]
default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']}
# Populate permission informations
permission_attrs = [
'cn',
'groupPermission',
]
result = 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')
permission_sync_to_user()
for a in app:
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)
@is_unit_operation(['permission', 'app'])
def permission_add(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True):
"""
Create a new permission for a specific application
Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail
permission -- name of the permission ("main" by default)
urls -- list of urls to specify for the permission
"""
from yunohost.domain import _normalize_domain_path
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
# Validate uniqueness of permission in LDAP
permission_name = str(permission + '.' + app) # str(...) Fix encoding issue
conflict = ldap.get_conflict({
'cn': permission_name
}, base_dn='ou=permission,dc=yunohost,dc=org')
if conflict:
raise YunohostError('permission_already_exist', permission=permission, app=app)
# Get random GID
all_gid = {x.gr_gid for x in grp.getgrall()}
uid_guid_found = False
while not uid_guid_found:
gid = str(random.randint(200, 99999))
uid_guid_found = gid not in all_gid
attr_dict = {
'objectClass': ['top', 'permissionYnh', 'posixGroup'],
'cn': permission_name,
'gidNumber': gid,
}
if default_allow:
attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org'
if urls:
attr_dict['URL'] = []
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.start()
if ldap.add('cn=%s,ou=permission' % permission_name, attr_dict):
if sync_perm:
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')
@is_unit_operation(['permission', 'app'])
def permission_update(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True):
"""
Update a permission for a specific application
Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail
permission -- name of the permission ("main" by default)
add_url -- Add a new url for a permission
remove_url -- Remove a url for a permission
"""
from yunohost.domain import _normalize_domain_path
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
permission_name = str(permission + '.' + app) # str(...) Fix encoding issue
# Populate permission informations
result = ldap.search(base='ou=permission,dc=yunohost,dc=org',
filter='cn=' + permission_name, attrs=['URL'])
if not result:
raise YunohostError('permission_not_found', permission=permission, app=app)
permission_obj = result[0]
if 'URL' not in permission_obj:
permission_obj['URL'] = []
url = set(permission_obj['URL'])
if add_url:
for u in add_url:
domain = u[:u.index('/')]
path = u[u.index('/'):]
domain, path = _normalize_domain_path(domain, path)
url.add(domain + path)
if remove_url:
for u in remove_url:
domain = u[:u.index('/')]
path = u[u.index('/'):]
domain, path = _normalize_domain_path(domain, path)
url.discard(domain + path)
if url == set(permission_obj['URL']):
logger.warning(m18n.n('permission_update_nothing_to_do'))
return user_permission_list(app, permission)
operation_logger.start()
if ldap.update('cn=%s,ou=permission' % permission_name, {'cn': permission_name, 'URL': url}):
if sync_perm:
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')
@is_unit_operation(['permission', 'app'])
def permission_remove(operation_logger, app, permission, force=False, sync_perm=True):
"""
Remove a permission for a specific application
Keyword argument:
app -- an application OR sftp, xmpp (metronome), mail
permission -- name of the permission ("main" by default)
"""
if permission == "main" and not force:
raise YunohostError('remove_main_permission_not_allowed')
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
operation_logger.start()
if not ldap.remove('cn=%s,ou=permission' % str(permission + '.' + app)):
raise YunohostError('permission_deletion_failed', permission=permission, app=app)
if sync_perm:
permission_sync_to_user()
logger.debug(m18n.n('permission_deleted', permission=permission, app=app))
def permission_sync_to_user(force=False):
"""
Sychronise the inheritPermission attribut in the permission object from the
user<->group link and the group<->permission link
Keyword argument:
force -- Force to recreate all attributes. Used generally with the
backup which 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
from yunohost.app import app_ssowatconf
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
permission_attrs = [
'cn',
'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',
'(objectclass=permissionYnh)',
['cn', 'inheritPermission', 'groupPermission', 'memberUid']):
if 'groupPermission' not in per:
continue
user_permission = set()
for group in per['groupPermission']:
group = group.split("=")[1].split(",")[0]
if 'member' not in group_info[group]:
continue
for user in group_info[group]['member']:
user_permission.add(user)
if 'inheritPermission' not in per:
per['inheritPermission'] = []
if 'memberUid' not in per:
per['memberUid'] = []
uid_val = [v.split("=")[1].split(",")[0] for v in user_permission]
if user_permission == set(per['inheritPermission']) and set(uid_val) == set(per['memberUid']) and not force:
continue
inheritPermission = {'inheritPermission': user_permission, 'memberUid': uid_val}
if force:
if per['groupPermission']:
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': []}):
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()
# Reload unscd, otherwise the group ain't propagated to the LDAP database
os.system('nscd --invalidate=passwd')
os.system('nscd --invalidate=group')

View file

@ -4,7 +4,6 @@ from yunohost.utils.error import YunohostError
from yunohost.app import app_install, app_remove
from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path
# Get main domain
maindomain = _get_maindomain()

View file

@ -10,6 +10,8 @@ from yunohost.app import _is_installed
from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount
from yunohost.domain import _get_maindomain
from yunohost.utils.error import YunohostError
from yunohost.user import user_permission_list
from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps
# Get main domain
maindomain = ""
@ -72,6 +74,18 @@ def teardown_function(function):
shutil.rmtree("/opt/test_backup_output_directory")
@pytest.fixture(autouse=True)
def check_LDAP_db_integrity_call():
check_LDAP_db_integrity()
yield
check_LDAP_db_integrity()
@pytest.fixture(autouse=True)
def check_permission_for_apps_call():
check_permission_for_apps()
yield
check_permission_for_apps()
#
# Helpers #
#
@ -517,6 +531,7 @@ def _test_backup_and_restore_app(app):
# Uninstall the app
app_remove(app)
assert not app_is_installed(app)
assert app not in user_permission_list()['permissions']
# Restore the app
backup_restore(system=None, name=archives[0],
@ -524,6 +539,11 @@ def _test_backup_and_restore_app(app):
assert app_is_installed(app)
# Check permission
per_list = user_permission_list()['permissions']
assert app in per_list
assert "main" in per_list[app]
#
# Some edge cases #
#

View file

@ -0,0 +1,419 @@
import pytest
from moulinette.core import MoulinetteError
from yunohost.app import app_install, app_remove, app_change_url, app_list
from yunohost.user import user_list, user_create, user_permission_list, user_delete, user_group_list, user_group_delete, user_permission_add, user_permission_remove, user_permission_clear
from yunohost.permission import permission_add, permission_update, permission_remove
from yunohost.domain import _get_maindomain
from yunohost.utils.error import YunohostError
# Get main domain
maindomain = _get_maindomain()
def clean_user_groups_permission():
for u in user_list()['users']:
user_delete(u)
for g in user_group_list()['groups']:
if g != "all_users":
user_group_delete(g)
for a, per in user_permission_list()['permissions'].items():
if a in ['wiki', 'blog', 'site']:
for p in per:
permission_remove(a, p, force=True, sync_perm=False)
def setup_function(function):
clean_user_groups_permission()
user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh")
user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh")
permission_add("wiki", "main", [maindomain + "/wiki"], sync_perm=False)
permission_add("blog", "main", sync_perm=False)
user_permission_add(["blog"], "main", group="alice")
def teardown_function(function):
clean_user_groups_permission()
try:
app_remove("permissions_app")
except:
pass
@pytest.fixture(autouse=True)
def check_LDAP_db_integrity_call():
check_LDAP_db_integrity()
yield
check_LDAP_db_integrity()
def check_LDAP_db_integrity():
# Here we check that all attributes in all object are sychronized.
# Here is the list of attributes per object:
# user : memberOf, permission
# group : member, permission
# permission : groupPermission, inheritPermission
#
# The idea is to check that all attributes on all sides of object are sychronized.
# One part should be done automatically by the "memberOf" overlay of LDAP.
# The other part is done by the the "permission_sync_to_user" function of the permission module
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
user_search = ldap.search('ou=users,dc=yunohost,dc=org',
'(&(objectclass=person)(!(uid=root))(!(uid=nobody)))',
['uid', 'memberOf', 'permission'])
group_search = ldap.search('ou=groups,dc=yunohost,dc=org',
'(objectclass=groupOfNamesYnh)',
['cn', 'member', 'memberUid', 'permission'])
permission_search = ldap.search('ou=permission,dc=yunohost,dc=org',
'(objectclass=permissionYnh)',
['cn', 'groupPermission', 'inheritPermission', 'memberUid'])
user_map = {u['uid'][0]: u for u in user_search}
group_map = {g['cn'][0]: g for g in group_search}
permission_map = {p['cn'][0]: p for p in permission_search}
for user in user_search:
user_dn = 'uid=' + user['uid'][0] + ',ou=users,dc=yunohost,dc=org'
group_list = [m.split("=")[1].split(",")[0] for m in user['memberOf']]
permission_list = []
if 'permission' in user:
permission_list = [m.split("=")[1].split(",")[0] for m in user['permission']]
for group in group_list:
assert user_dn in group_map[group]['member']
for permission in permission_list:
assert user_dn in permission_map[permission]['inheritPermission']
for permission in permission_search:
permission_dn = 'cn=' + permission['cn'][0] + ',ou=permission,dc=yunohost,dc=org'
user_list = []
group_list = []
if 'inheritPermission' in permission:
user_list = [m.split("=")[1].split(",")[0] for m in permission['inheritPermission']]
assert set(user_list) == set(permission['memberUid'])
if 'groupPermission' in permission:
group_list = [m.split("=")[1].split(",")[0] for m in permission['groupPermission']]
for user in user_list:
assert permission_dn in user_map[user]['permission']
for group in group_list:
assert permission_dn in group_map[group]['permission']
if 'member' in group_map[group]:
user_list_in_group = [m.split("=")[1].split(",")[0] for m in group_map[group]['member']]
assert set(user_list_in_group) <= set(user_list)
for group in group_search:
group_dn = 'cn=' + group['cn'][0] + ',ou=groups,dc=yunohost,dc=org'
user_list = []
permission_list = []
if 'member' in group:
user_list = [m.split("=")[1].split(",")[0] for m in group['member']]
if group['cn'][0] in user_list:
# If it's the main group of the user it's normal that it is not in the memberUid
g_list = list(user_list)
g_list.remove(group['cn'][0])
if 'memberUid' in group:
assert set(g_list) == set(group['memberUid'])
else:
assert g_list == []
else:
assert set(user_list) == set(group['memberUid'])
if 'permission' in group:
permission_list = [m.split("=")[1].split(",")[0] for m in group['permission']]
for user in user_list:
assert group_dn in user_map[user]['memberOf']
for permission in permission_list:
assert group_dn in permission_map[permission]['groupPermission']
if 'inheritPermission' in permission_map:
allowed_user_list = [m.split("=")[1].split(",")[0] for m in permission_map[permission]['inheritPermission']]
assert set(user_list) <= set(allowed_user_list)
def check_permission_for_apps():
# We check that the for each installed apps we have at last the "main" permission
# and we don't have any permission linked to no apps. The only exception who is not liked to an app
# is mail, metronome, and sftp
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
permission_search = ldap.search('ou=permission,dc=yunohost,dc=org',
'(objectclass=permissionYnh)',
['cn', 'groupPermission', 'inheritPermission', 'memberUid'])
installed_apps = {app['id'] for app in app_list(installed=True)['apps']}
permission_list_set = {permission['cn'][0].split(".")[1] for permission in permission_search}
extra_service_permission = set(['mail', 'metronome'])
if 'sftp' in permission_list_set:
extra_service_permission.add('sftp')
assert installed_apps == permission_list_set - extra_service_permission
#
# List functions
#
def test_list_permission():
res = user_permission_list()['permissions']
assert "wiki" in res
assert "main" in res['wiki']
assert "blog" in res
assert "main" in res['blog']
assert "mail" in res
assert "main" in res['mail']
assert "metronome" in res
assert "main" in res['metronome']
assert ["all_users"] == res['wiki']['main']['allowed_groups']
assert ["alice"] == res['blog']['main']['allowed_groups']
assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users'])
assert ["alice"] == res['blog']['main']['allowed_users']
assert [maindomain + "/wiki"] == res['wiki']['main']['URL']
#
# Create - Remove functions
#
def test_add_permission_1():
permission_add("site", "test")
res = user_permission_list()['permissions']
assert "site" in res
assert "test" in res['site']
assert "all_users" in res['site']['test']['allowed_groups']
assert set(["alice", "bob"]) == set(res['site']['test']['allowed_users'])
def test_add_permission_2():
permission_add("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
#
def test_add_bad_permission():
# Create permission with same name
with pytest.raises(YunohostError):
permission_add("wiki", "main")
def test_remove_bad_permission():
# Remove not existant permission
with pytest.raises(MoulinetteError):
permission_remove("non_exit", "main", force=True)
res = user_permission_list()['permissions']
assert "wiki" in res
assert "main" in res['wiki']
assert "blog" in res
assert "main" in res['blog']
assert "mail" in res
assert "main" in res ['mail']
assert "metronome" in res
assert "main" in res['metronome']
def test_remove_main_permission():
with pytest.raises(YunohostError):
permission_remove("blog", "main")
res = user_permission_list()['permissions']
assert "mail" in res
assert "main" in res['mail']
#
# Update functions
#
# user side functions
def test_allow_first_group():
# Remove permission to all_users and define per users
user_permission_add(["wiki"], "main", group="alice")
res = user_permission_list()['permissions']
assert ['alice'] == res['wiki']['main']['allowed_users']
assert ['alice'] == res['wiki']['main']['allowed_groups']
def test_allow_other_group():
# Allow new user in a permission
user_permission_add(["blog"], "main", group="bob")
res = user_permission_list()['permissions']
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users'])
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_groups'])
def test_disallow_group_1():
# Disallow a user in a permission
user_permission_remove(["blog"], "main", group="alice")
res = user_permission_list()['permissions']
assert [] == res['blog']['main']['allowed_users']
assert [] == res['blog']['main']['allowed_groups']
def test_allow_group_1():
# Allow a user when he is already allowed
user_permission_add(["blog"], "main", group="alice")
res = user_permission_list()['permissions']
assert ["alice"] == res['blog']['main']['allowed_users']
assert ["alice"] == res['blog']['main']['allowed_groups']
def test_disallow_group_1():
# Disallow a user when he is already disallowed
user_permission_remove(["blog"], "main", group="bob")
res = user_permission_list()['permissions']
assert ["alice"] == res['blog']['main']['allowed_users']
assert ["alice"] == res['blog']['main']['allowed_groups']
def test_reset_permission():
# Reset permission
user_permission_clear(["blog"], "main")
res = user_permission_list()['permissions']
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users'])
assert ["all_users"] == res['blog']['main']['allowed_groups']
# 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
#
def test_disallow_bad_group_1():
# Disallow a group when the group all_users is allowed
with pytest.raises(YunohostError):
user_permission_remove("wiki", "main", group="alice")
res = user_permission_list()['permissions']
assert ["all_users"] == res['wiki']['main']['allowed_groups']
assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users'])
def test_allow_bad_user():
# Allow a non existant group
with pytest.raises(YunohostError):
user_permission_add(["blog"], "main", group="not_exist")
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():
# Disallow a non existant group
with pytest.raises(YunohostError):
user_permission_remove(["blog"], "main", group="not_exist")
res = user_permission_list()['permissions']
assert ["alice"] == res['blog']['main']['allowed_groups']
assert ["alice"] == res['blog']['main']['allowed_users']
def test_allow_bad_permission_1():
# Allow a user to a non existant permission
with pytest.raises(YunohostError):
user_permission_add(["wiki"], "not_exit", group="alice")
def test_allow_bad_permission_2():
# Allow a user to a non existant permission
with pytest.raises(YunohostError):
user_permission_add(["not_exit"], "main", group="alice")
#
# Application interaction
#
def test_install_app():
app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
res = user_permission_list()['permissions']
assert "permissions_app" in res
assert "main" in res['permissions_app']
assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL']
assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL']
assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL']
assert ["all_users"] == res['permissions_app']['main']['allowed_groups']
assert set(["alice", "bob"]) == set(res['permissions_app']['main']['allowed_users'])
assert ["alice"] == res['permissions_app']['admin']['allowed_groups']
assert ["alice"] == res['permissions_app']['admin']['allowed_users']
assert ["all_users"] == res['permissions_app']['dev']['allowed_groups']
assert set(["alice", "bob"]) == set(res['permissions_app']['dev']['allowed_users'])
def test_remove_app():
app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
app_remove("permissions_app")
res = user_permission_list()['permissions']
assert "permissions_app" not in res
def test_change_url():
app_install("./tests/apps/permissions_app_ynh",
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
res = user_permission_list()['permissions']
assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL']
assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL']
assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL']
app_change_url("permissions_app", maindomain, "/newchangeurl")
res = user_permission_list()['permissions']
assert [maindomain + "/newchangeurl"] == res['permissions_app']['main']['URL']
assert [maindomain + "/newchangeurl/admin"] == res['permissions_app']['admin']['URL']
assert [maindomain + "/newchangeurl/dev"] == res['permissions_app']['dev']['URL']

View file

@ -0,0 +1,210 @@
import pytest
from moulinette.core import MoulinetteError
from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_add, user_group_delete, user_group_update, user_group_info
from yunohost.domain import _get_maindomain
from yunohost.utils.error import YunohostError
from yunohost.tests.test_permission import check_LDAP_db_integrity
# Get main domain
maindomain = _get_maindomain()
def clean_user_groups():
for u in user_list()['users']:
user_delete(u)
for g in user_group_list()['groups']:
if g != "all_users":
user_group_delete(g)
def setup_function(function):
clean_user_groups()
user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh")
user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh")
user_create("jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh")
user_group_add("dev")
user_group_add("apps")
user_group_update("dev", add_user=["alice"])
user_group_update("apps", add_user=["bob"])
def teardown_function(function):
clean_user_groups()
@pytest.fixture(autouse=True)
def check_LDAP_db_integrity_call():
check_LDAP_db_integrity()
yield
check_LDAP_db_integrity()
#
# List functions
#
def test_list_users():
res = user_list()['users']
assert "alice" in res
assert "bob" in res
assert "jack" in res
def test_list_groups():
res = user_group_list()['groups']
assert "all_users" in res
assert "alice" in res
assert "bob" in res
assert "jack" in res
for u in ["alice", "bob", "jack"]:
assert u in res
assert u in res[u]['members']
assert u in res["all_users"]['members']
#
# Create - Remove functions
#
def test_create_user():
user_create("albert", "Albert", "Good", "alber@" + maindomain, "test123Ynh")
group_res = user_group_list()['groups']
assert "albert" in user_list()['users']
assert "albert" in group_res
assert "albert" in group_res['albert']['members']
assert "albert" in group_res['all_users']['members']
def test_del_user():
user_delete("alice")
group_res = user_group_list()['groups']
assert "alice" not in user_list()
assert "alice" not in group_res
assert "alice" not in group_res['all_users']['members']
def test_add_group():
user_group_add("adminsys")
group_res = user_group_list()['groups']
assert "adminsys" in group_res
assert "members" not in group_res['adminsys']
def test_del_group():
user_group_delete("dev")
group_res = user_group_list()['groups']
assert "dev" not in group_res
#
# Error on create / remove function
#
def test_add_bad_user_1():
# Check email already exist
with pytest.raises(MoulinetteError):
user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh")
def test_add_bad_user_2():
# Check to short password
with pytest.raises(MoulinetteError):
user_create("other", "Alice", "White", "other@" + maindomain, "12")
def test_add_bad_user_3():
# Check user already exist
with pytest.raises(MoulinetteError):
user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh")
def test_del_bad_user_1():
# Check user not found
with pytest.raises(MoulinetteError):
user_delete("not_exit")
def test_add_bad_group_1():
# Check groups already exist with special group "all_users"
with pytest.raises(YunohostError):
user_group_add("all_users")
def test_add_bad_group_2():
# Check groups already exist (for standard groups)
with pytest.raises(MoulinetteError):
user_group_add("dev")
def test_del_bad_group_1():
# Check not allowed to remove this groups
with pytest.raises(YunohostError):
user_group_delete("all_users")
def test_del_bad_group_2():
# Check groups not found
with pytest.raises(MoulinetteError):
user_group_delete("not_exit")
#
# Update function
#
def test_update_user_1():
user_update("alice", firstname="NewName", lastname="NewLast")
info = user_info("alice")
assert "NewName" == info['firstname']
assert "NewLast" == info['lastname']
def test_update_group_1():
user_group_update("dev", add_user=["bob"])
group_res = user_group_list()['groups']
assert set(["alice", "bob"]) == set(group_res['dev']['members'])
def test_update_group_2():
# Try to add a user in a group when the user is already in
user_group_update("apps", add_user=["bob"])
group_res = user_group_list()['groups']
assert ["bob"] == group_res['apps']['members']
def test_update_group_3():
# Try to remove a user in a group
user_group_update("apps", remove_user=["bob"])
group_res = user_group_list()['groups']
assert "members" not in group_res['apps']
def test_update_group_4():
# Try to remove a user in a group when it is not already in
user_group_update("apps", remove_user=["jack"])
group_res = user_group_list()['groups']
assert ["bob"] == group_res['apps']['members']
#
# Error on update functions
#
def test_bad_update_user_1():
# Check user not found
with pytest.raises(YunohostError):
user_update("not_exit", firstname="NewName", lastname="NewLast")
def bad_update_group_1():
# Check groups not found
with pytest.raises(YunohostError):
user_group_update("not_exit", add_user=["alice"])
def test_bad_update_group_2():
# Check remove user in groups "all_users" not allowed
with pytest.raises(YunohostError):
user_group_update("all_users", remove_user=["alice"])
def test_bad_update_group_3():
# Check remove user in it own group not allowed
with pytest.raises(YunohostError):
user_group_update("alice", remove_user=["alice"])
def test_bad_update_group_1():
# Check add bad user in group
with pytest.raises(YunohostError):
user_group_update("dev", add_user=["not_exist"])
assert "not_exist" not in user_group_list()["groups"]["dev"]

View file

@ -82,6 +82,12 @@ def tools_ldapinit():
except Exception as e:
logger.warn("Error when trying to inject '%s' -> '%s' into ldap: %s" % (rdn, attr_dict, e))
for rdn, attr_dict in ldap_map['depends_children'].items():
try:
ldap.add(rdn, attr_dict)
except Exception as e:
logger.warn("Error when trying to inject '%s' -> '%s' into ldap: %s" % (rdn, attr_dict, e))
admin_dict = {
'cn': 'admin',
'uid': 'admin',

View file

@ -26,6 +26,7 @@
import os
import re
import pwd
import grp
import json
import crypt
import random
@ -117,7 +118,6 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
"""
from yunohost.domain import domain_list, _get_maindomain
from yunohost.hook import hook_callback
from yunohost.app import app_ssowatconf
from yunohost.utils.password import assert_password_is_strong_enough
from yunohost.utils.ldap import _get_ldap_interface
@ -129,7 +129,8 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
# Validate uniqueness of username and mail in LDAP
ldap.validate_uniqueness({
'uid': username,
'mail': mail
'mail': mail,
'cn': username
})
# Validate uniqueness of username in system users
@ -156,7 +157,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
# Get random UID/GID
all_uid = {x.pw_uid for x in pwd.getpwall()}
all_gid = {x.pw_gid for x in pwd.getpwall()}
all_gid = {x.gr_gid for x in grp.getgrall()}
uid_guid_found = False
while not uid_guid_found:
@ -166,7 +167,7 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
# Adapt values for LDAP
fullname = '%s %s' % (firstname, lastname)
attr_dict = {
'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount'],
'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh'],
'givenName': firstname,
'sn': lastname,
'displayName': fullname,
@ -207,10 +208,6 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
# Invalidate passwd to take user creation into account
subprocess.call(['nscd', '-i', 'passwd'])
# Update SFTP user group
memberlist = ldap.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
memberlist.append(username)
if ldap.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}):
try:
# Attempt to create user home folder
subprocess.check_call(
@ -219,9 +216,15 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
if not os.path.isdir('/home/{0}'.format(username)):
logger.warning(m18n.n('user_home_creation_failed'),
exc_info=1)
app_ssowatconf()
# Create group for user and add to group 'all_users'
user_group_add(groupname=username, gid=uid, sync_perm=False)
user_group_update(groupname=username, add_user=username, force=True, sync_perm=False)
user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=True)
# TODO: Send a welcome mail to user
logger.success(m18n.n('user_created'))
hook_callback('post_user_create',
args=[username, mail, password, firstname, lastname])
@ -240,7 +243,6 @@ def user_delete(operation_logger, username, purge=False):
purge
"""
from yunohost.app import app_ssowatconf
from yunohost.hook import hook_callback
from yunohost.utils.ldap import _get_ldap_interface
@ -251,20 +253,24 @@ def user_delete(operation_logger, username, purge=False):
# Invalidate passwd to take user deletion into account
subprocess.call(['nscd', '-i', 'passwd'])
# Update SFTP user group
memberlist = ldap.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
try:
memberlist.remove(username)
except:
pass
if ldap.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}):
if purge:
subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)])
else:
raise YunohostError('user_deletion_failed')
app_ssowatconf()
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])
@ -295,18 +301,18 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail=
from yunohost.utils.password import assert_password_is_strong_enough
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
new_attr_dict = {}
domains = domain_list()['domains']
# Populate user informations
ldap = _get_ldap_interface()
attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
result = ldap.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch)
if not result:
raise YunohostError('user_unknown', user=username)
user = result[0]
# Get modifications from arguments
new_attr_dict = {}
if firstname:
new_attr_dict['givenName'] = firstname # TODO: Validate
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + user['sn'][0]
@ -409,7 +415,7 @@ def user_info(username):
'cn', 'mail', 'uid', 'maildrop', 'givenName', 'sn', 'mailuserquota'
]
if len(username.split('@')) is 2:
if len(username.split('@')) == 2:
filter = 'mail=' + username
else:
filter = 'uid=' + username
@ -447,6 +453,8 @@ def user_info(username):
if service_status("dovecot")["status"] != "running":
logger.warning(m18n.n('mailbox_used_space_dovecot_down'))
elif not user_permission_list(app="mail", permission="main", username=username)['permissions']:
logger.warning(m18n.n('mailbox_disabled', user=username))
else:
cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0]
cmd_result = subprocess.check_output(cmd, stderr=subprocess.STDOUT,
@ -477,10 +485,307 @@ def user_info(username):
else:
raise YunohostError('user_info_failed')
#
# Group subcategory
#
def user_group_list(fields=None):
"""
List users
Keyword argument:
filter -- LDAP filter used to search
offset -- Starting number for user fetching
limit -- Maximum number of user fetched
fields -- fields to fetch
"""
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
group_attr = {
'cn': 'groupname',
'member': 'members',
'permission': 'permission'
}
attrs = ['cn']
groups = {}
if fields:
keys = group_attr.keys()
for attr in fields:
if attr in keys:
attrs.append(attr)
else:
raise YunohostError('field_invalid', attr)
else:
attrs = ['cn', 'member']
result = ldap.search('ou=groups,dc=yunohost,dc=org',
'(objectclass=groupOfNamesYnh)',
attrs)
for group in result:
# The group "admins" should be hidden for the user
if group_attr['cn'] == "admins":
continue
entry = {}
for attr, values in group.items():
if values:
if attr == "member":
entry[group_attr[attr]] = []
for v in values:
entry[group_attr[attr]].append(v.split("=")[1].split(",")[0])
elif attr == "permission":
entry[group_attr[attr]] = {}
for v in values:
permission = v.split("=")[1].split(",")[0].split(".")[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[groupname] = entry
return {'groups': groups}
@is_unit_operation([('groupname', 'user')])
def user_group_add(operation_logger, groupname, gid=None, sync_perm=True):
"""
Create group
Keyword argument:
groupname -- Must be unique
"""
from yunohost.permission import permission_sync_to_user
from yunohost.utils.ldap import _get_ldap_interface
operation_logger.start()
ldap = _get_ldap_interface()
# Validate uniqueness of groupname in LDAP
conflict = ldap.get_conflict({
'cn': groupname
}, base_dn='ou=groups,dc=yunohost,dc=org')
if conflict:
raise YunohostError('group_name_already_exist', name=groupname)
# Validate uniqueness of groupname in system group
all_existing_groupnames = {x.gr_name for x in grp.getgrall()}
if groupname in all_existing_groupnames:
raise YunohostError('system_groupname_exists')
if not gid:
# Get random GID
all_gid = {x.gr_gid for x in grp.getgrall()}
uid_guid_found = False
while not uid_guid_found:
gid = str(random.randint(200, 99999))
uid_guid_found = gid not in all_gid
attr_dict = {
'objectClass': ['top', 'groupOfNamesYnh', 'posixGroup'],
'cn': groupname,
'gidNumber': gid,
}
if ldap.add('cn=%s,ou=groups' % groupname, attr_dict):
logger.success(m18n.n('group_created', group=groupname))
if sync_perm:
permission_sync_to_user()
return {'name': groupname}
raise YunohostError('group_creation_failed', group=groupname)
@is_unit_operation([('groupname', 'user')])
def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
"""
Delete user
Keyword argument:
groupname -- Groupname to delete
"""
from yunohost.permission import permission_sync_to_user
from yunohost.utils.ldap import _get_ldap_interface
forbidden_groups = ["all_users", "admins"] + user_list(fields=['uid'])['users'].keys()
if not force and groupname in forbidden_groups:
raise YunohostError('group_deletion_not_allowed', group=groupname)
operation_logger.start()
ldap = _get_ldap_interface()
if not ldap.remove('cn=%s,ou=groups' % groupname):
raise YunohostError('group_deletion_failed', group=groupname)
logger.success(m18n.n('group_deleted', group=groupname))
if sync_perm:
permission_sync_to_user()
@is_unit_operation([('groupname', 'user')])
def user_group_update(operation_logger, groupname, add_user=None, remove_user=None, force=False, sync_perm=True):
"""
Update user informations
Keyword argument:
groupname -- Groupname to update
add_user -- User to add in group
remove_user -- User to remove in group
"""
from yunohost.permission import permission_sync_to_user
from yunohost.utils.ldap import _get_ldap_interface
if (groupname == 'all_users' or groupname == 'admins') and not force:
raise YunohostError('edit_group_not_allowed', group=groupname)
ldap = _get_ldap_interface()
# Populate group informations
attrs_to_fetch = ['member']
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()}
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:
raise YunohostError('user_unknown', user=user)
for user in add_user:
userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org"
if userDN in group['member']:
logger.warning(m18n.n('user_already_in_group', user=user, group=groupname))
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:
logger.warning(m18n.n('user_not_in_group', user=user, group=groupname))
# Sychronise memberUid with member (to keep the posix group structure)
# In posixgroup the main group of each user is only written in the gid number of the user
for member in new_group_list['member']:
member_Uid = member.split("=")[1].split(",")[0]
# Don't add main user in the group.
# Note that in the Unix system the main user of the group is linked by the gid in the user attribute.
# So the main user need to be not in the memberUid list of his own group.
if member_Uid != groupname:
new_group_list['memberUid'].add(member_Uid)
operation_logger.start()
if new_group_list['member'] != set(group['member']):
if not 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))
if sync_perm:
permission_sync_to_user()
return user_group_info(groupname)
def user_group_info(groupname):
"""
Get user informations
Keyword argument:
groupname -- Groupname to get informations
"""
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
group_attrs = [
'cn', 'member', 'permission'
]
result = ldap.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs)
if not result:
raise YunohostError('group_unknown', group=groupname)
group = result[0]
result_dict = {
'groupname': group['cn'][0],
'member': None
}
if 'member' in group:
result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']}
return result_dict
#
# Permission subcategory
#
def user_permission_list(app=None, permission=None, username=None, group=None, sync_perm=True):
import yunohost.permission
return yunohost.permission.user_permission_list(app, permission, username, group)
@is_unit_operation([('app', 'user')])
def user_permission_add(operation_logger, app, permission="main", username=None, group=None, sync_perm=True):
import yunohost.permission
return yunohost.permission.user_permission_update(operation_logger, app, permission=permission,
add_username=username, add_group=group,
del_username=None, del_group=None,
sync_perm=sync_perm)
@is_unit_operation([('app', 'user')])
def user_permission_remove(operation_logger, app, permission="main", username=None, group=None, sync_perm=True):
import yunohost.permission
return yunohost.permission.user_permission_update(operation_logger, app, permission=permission,
add_username=None, add_group=None,
del_username=username, del_group=group,
sync_perm=sync_perm)
@is_unit_operation([('app', 'user')])
def user_permission_clear(operation_logger, app, permission=None, sync_perm=True):
import yunohost.permission
return yunohost.permission.user_permission_clear(operation_logger, app, permission,
sync_perm=sync_perm)
#
# SSH subcategory
#
#
import yunohost.ssh