Implement group management

This commit is contained in:
Josué Tille 2018-11-25 22:50:48 +01:00
parent 7b8d668846
commit bb892bb1a4
No known key found for this signature in database
GPG key ID: D5E068C6DFA8681D
2 changed files with 346 additions and 31 deletions

View file

@ -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",

View file

@ -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
#