mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Implement group management
This commit is contained in:
parent
7b8d668846
commit
bb892bb1a4
2 changed files with 346 additions and 31 deletions
|
@ -180,6 +180,7 @@
|
|||
"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}",
|
||||
"executing_command": "Executing command '{command:s}'…",
|
||||
"executing_script": "Executing script '{script:s}'…",
|
||||
"extracting": "Extracting…",
|
||||
|
@ -207,6 +208,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_alread_allowed": "Group '{group:s}' already allowed to access to permission '{permission:s}' for app '{app:s}'",
|
||||
"group_alread_disallowed": "Group '{group:s}' already disallowed to access to permission '{permission:s}' for app '{app:s}'",
|
||||
"group_name_already_exist": "Group {name:s} already exist",
|
||||
"group_created": "Group creation success",
|
||||
"group_creation_failed": "Group creation failed",
|
||||
"group_deleted": "Group deleted",
|
||||
"group_deletion_failed": "Group deletion failed",
|
||||
"group_deletion_not_allowed": "You are not allowed to remove the main group of the user {user:s}",
|
||||
"group_info_failed": "Group info failed",
|
||||
"group_unknown": "Groupe {group:s} unknown",
|
||||
"group_updated": "Groupe updated",
|
||||
"group_update_failed": "groupe update failed",
|
||||
"hook_exec_failed": "Script execution failed: {path:s}",
|
||||
"hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}",
|
||||
"hook_list_by_invalid": "Invalid property to list hook by",
|
||||
|
@ -244,13 +257,20 @@
|
|||
"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_service_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_tools_maindomain": "Make '{}' as main domain",
|
||||
"log_tools_migrations_migrate_forward": "Migrate forward",
|
||||
"log_tools_migrations_migrate_backward": "Migrate backward",
|
||||
|
@ -370,11 +390,23 @@
|
|||
"pattern_port_or_range": "Must be a valid port number (i.e. 0-65535) or range of ports (e.g. 100:200)",
|
||||
"pattern_positive_number": "Must be a positive number",
|
||||
"pattern_username": "Must be lower-case alphanumeric and underscore characters only",
|
||||
"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",
|
||||
"premission_creation_failled": "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_updated": "Permission '{permission:s}' for app {app:s} updated",
|
||||
"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' 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}",
|
||||
"restore_action_required": "You must specify something to restore",
|
||||
"restore_already_installed_app": "An app is already installed with the id '{app:s}'",
|
||||
"restore_app_failed": "Unable to restore the app '{app:s}'",
|
||||
|
@ -459,6 +491,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",
|
||||
"unbackup_app": "App '{app:s}' will not be saved",
|
||||
|
@ -474,12 +507,14 @@
|
|||
"upnp_disabled": "UPnP has been disabled",
|
||||
"upnp_enabled": "UPnP has been enabled",
|
||||
"upnp_port_open_failed": "Unable to open UPnP ports",
|
||||
"user_alread_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",
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
import os
|
||||
import re
|
||||
import pwd
|
||||
import grp
|
||||
import json
|
||||
import crypt
|
||||
import random
|
||||
|
@ -123,7 +124,8 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
# Validate uniqueness of username and mail in LDAP
|
||||
auth.validate_uniqueness({
|
||||
'uid': username,
|
||||
'mail': mail
|
||||
'mail': mail,
|
||||
'cn': username
|
||||
})
|
||||
|
||||
# Validate uniqueness of username in system users
|
||||
|
@ -150,7 +152,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
|
||||
# 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:
|
||||
|
@ -160,7 +162,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
# 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,
|
||||
|
@ -201,25 +203,26 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
# Invalidate passwd to take user creation into account
|
||||
subprocess.call(['nscd', '-i', 'passwd'])
|
||||
|
||||
# Update SFTP user group
|
||||
memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
|
||||
memberlist.append(username)
|
||||
if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}):
|
||||
try:
|
||||
# Attempt to create user home folder
|
||||
subprocess.check_call(
|
||||
['su', '-', username, '-c', "''"])
|
||||
except subprocess.CalledProcessError:
|
||||
if not os.path.isdir('/home/{0}'.format(username)):
|
||||
logger.warning(m18n.n('user_home_creation_failed'),
|
||||
exc_info=1)
|
||||
app_ssowatconf(auth)
|
||||
# TODO: Send a welcome mail to user
|
||||
logger.success(m18n.n('user_created'))
|
||||
hook_callback('post_user_create',
|
||||
args=[username, mail, password, firstname, lastname])
|
||||
try:
|
||||
# Attempt to create user home folder
|
||||
subprocess.check_call(
|
||||
['su', '-', username, '-c', "''"])
|
||||
except subprocess.CalledProcessError:
|
||||
if not os.path.isdir('/home/{0}'.format(username)):
|
||||
logger.warning(m18n.n('user_home_creation_failed'),
|
||||
exc_info=1)
|
||||
app_ssowatconf(auth)
|
||||
# TODO: Send a welcome mail to user
|
||||
logger.success(m18n.n('user_created'))
|
||||
# Create group for user and add to group 'ALL'
|
||||
user_group_add(auth, groupname=username, gid=uid)
|
||||
user_group_update(auth, groupname=username, add_user=username, force=True)
|
||||
user_group_update(auth, 'ALL', add_user=username, force=True)
|
||||
|
||||
return {'fullname': fullname, 'username': username, 'mail': mail}
|
||||
hook_callback('post_user_create',
|
||||
args=[username, mail, password, firstname, lastname])
|
||||
|
||||
return {'fullname': fullname, 'username': username, 'mail': mail}
|
||||
|
||||
raise YunohostError('user_creation_failed')
|
||||
|
||||
|
@ -242,19 +245,24 @@ def user_delete(operation_logger, auth, username, purge=False):
|
|||
# Invalidate passwd to take user deletion into account
|
||||
subprocess.call(['nscd', '-i', 'passwd'])
|
||||
|
||||
# Update SFTP user group
|
||||
memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
|
||||
try:
|
||||
memberlist.remove(username)
|
||||
except:
|
||||
pass
|
||||
if auth.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)])
|
||||
if purge:
|
||||
subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
|
||||
else:
|
||||
raise YunohostError('user_deletion_failed')
|
||||
|
||||
user_group_delete(auth, username, force=True)
|
||||
|
||||
group_list = auth.search('ou=groups,dc=yunohost,dc=org',
|
||||
'(&(objectclass=groupOfNamesYnh)(memberUid=%s))'
|
||||
% username, ['cn'])
|
||||
for group in group_list:
|
||||
user_list = auth.search('ou=groups,dc=yunohost,dc=org',
|
||||
'cn=' + group['cn'][0],
|
||||
['memberUid'])[0]
|
||||
user_list['memberUid'].remove(username)
|
||||
if not auth.update('cn=%s,ou=groups' % group['cn'][0], user_list):
|
||||
raise YunohostError('group_update_failed')
|
||||
|
||||
app_ssowatconf(auth)
|
||||
|
||||
hook_callback('post_user_delete', args=[username, purge])
|
||||
|
@ -462,6 +470,278 @@ def user_info(auth, username):
|
|||
else:
|
||||
raise YunohostError('user_info_failed')
|
||||
|
||||
#
|
||||
# Group subcategory
|
||||
#
|
||||
#
|
||||
def user_group_list(auth, 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
|
||||
|
||||
"""
|
||||
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 MoulinetteError(errno.EINVAL,
|
||||
m18n.n('field_invalid', attr))
|
||||
else:
|
||||
attrs = ['cn', 'member']
|
||||
|
||||
result = auth.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(".")[0]
|
||||
pType = v.split("=")[1].split(",")[0].split(".")[1]
|
||||
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, auth, groupname,gid=None):
|
||||
"""
|
||||
Create group
|
||||
|
||||
Keyword argument:
|
||||
groupname -- Must be unique
|
||||
|
||||
"""
|
||||
from yunohost.app import app_ssowatconf
|
||||
from yunohost.permission import _permission_sync_to_user
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
# Validate uniqueness of groupname in LDAP
|
||||
conflict = auth.get_conflict({
|
||||
'cn': groupname
|
||||
}, base_dn='ou=groups,dc=yunohost,dc=org')
|
||||
if conflict:
|
||||
raise MoulinetteError(errno.EEXIST, m18n.n('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 MoulinetteError(errno.EEXIST, m18n.n('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 auth.add('cn=%s,ou=groups' % groupname, attr_dict):
|
||||
_permission_sync_to_user(auth)
|
||||
app_ssowatconf(auth)
|
||||
logger.success(m18n.n('group_created'))
|
||||
return {'name': groupname}
|
||||
|
||||
raise MoulinetteError(169, m18n.n('group_creation_failed'))
|
||||
|
||||
|
||||
@is_unit_operation([('groupname', 'user')])
|
||||
def user_group_delete(operation_logger, auth, groupname, force=False):
|
||||
"""
|
||||
Delete user
|
||||
|
||||
Keyword argument:
|
||||
groupname -- Groupname to delete
|
||||
|
||||
"""
|
||||
from yunohost.app import app_ssowatconf
|
||||
from yunohost.permission import _permission_sync_to_user
|
||||
|
||||
if not force and (groupname == 'ALL' or groupname == 'admins' or groupname in user_list(auth, ['uid'])['users']):
|
||||
raise MoulinetteError(errno.EPERM, m18n.n('group_deletion_not_allowed', user=groupname))
|
||||
|
||||
operation_logger.start()
|
||||
if not auth.remove('cn=%s,ou=groups' % groupname):
|
||||
raise MoulinetteError(169, m18n.n('group_deletion_failed'))
|
||||
|
||||
_permission_sync_to_user(auth)
|
||||
app_ssowatconf(auth)
|
||||
logger.success(m18n.n('group_deleted'))
|
||||
|
||||
|
||||
@is_unit_operation([('groupname', 'user')])
|
||||
def user_group_update(operation_logger, auth, groupname, add_user=None, remove_user=None, force=False):
|
||||
"""
|
||||
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.app import app_ssowatconf
|
||||
from yunohost.permission import _permission_sync_to_user
|
||||
|
||||
attrs_to_fetch = ['member']
|
||||
|
||||
if (groupname == 'ALL' or groupname == 'admins') and not force:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('edit_group_not_allowed', group=groupname))
|
||||
|
||||
# Populate group informations
|
||||
result = auth.search(base='ou=groups,dc=yunohost,dc=org',
|
||||
filter='cn=' + groupname, attrs=attrs_to_fetch)
|
||||
if not result:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('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'] = []
|
||||
|
||||
user_l = user_list(auth, ['uid'])['users']
|
||||
|
||||
if add_user:
|
||||
if not isinstance(add_user, list):
|
||||
add_user = [add_user]
|
||||
for user in add_user:
|
||||
if not user in user_l:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=user))
|
||||
userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org"
|
||||
if userDN in group['member']:
|
||||
logger.warning(m18n.n('user_alread_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:
|
||||
userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org"
|
||||
if user == groupname:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('remove_user_of_group_not_allowed', user=user, group=groupname))
|
||||
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 auth.update('cn=%s,ou=groups' % groupname, new_group_list):
|
||||
raise MoulinetteError(169, m18n.n('group_update_failed'))
|
||||
|
||||
_permission_sync_to_user(auth)
|
||||
logger.success(m18n.n('group_updated'))
|
||||
app_ssowatconf(auth)
|
||||
return user_group_info(auth, groupname)
|
||||
|
||||
|
||||
def user_group_info(auth, groupname):
|
||||
"""
|
||||
Get user informations
|
||||
|
||||
Keyword argument:
|
||||
groupname -- Groupname to get informations
|
||||
|
||||
"""
|
||||
group_attrs = [
|
||||
'cn', 'member', 'permission'
|
||||
]
|
||||
result = auth.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs)
|
||||
|
||||
if not result:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('group_unknown', group=groupname))
|
||||
else:
|
||||
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
|
||||
#
|
||||
#
|
||||
import yunohost.permission
|
||||
|
||||
def user_permission_list(auth, app=None, permission=None, username=None, group=None):
|
||||
return yunohost.permission.user_permission_list(auth, app, permission, username, group)
|
||||
|
||||
@is_unit_operation([('app', 'user')])
|
||||
def user_permission_add(operation_logger, auth, app, permission="main", username=None, group=None):
|
||||
return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission,
|
||||
add_username=username, add_group=group,
|
||||
del_username=None, del_group=None)
|
||||
|
||||
@is_unit_operation([('app', 'user')])
|
||||
def user_permission_remove(operation_logger, auth, app, permission="main", username=None, group=None):
|
||||
return yunohost.permission.user_permission_update(operation_logger, auth, app, permission=permission,
|
||||
add_username=None, add_group=None,
|
||||
del_username=username, del_group=group)
|
||||
|
||||
@is_unit_operation([('app', 'user')])
|
||||
def user_permission_clear(operation_logger, auth, app, permission=None):
|
||||
return yunohost.permission.user_permission_clear(operation_logger, auth, app, permission)
|
||||
|
||||
#
|
||||
# SSH subcategory
|
||||
#
|
||||
|
|
Loading…
Add table
Reference in a new issue