Update and review yunohost.user

This commit is contained in:
Jerome Lebleu 2014-03-27 03:03:14 +01:00
parent 447748175e
commit 8be16b85fa
2 changed files with 208 additions and 209 deletions

View file

@ -49,6 +49,7 @@ _global:
parameters: parameters:
uri: ldap://localhost:389 uri: ldap://localhost:389
base_dn: dc=yunohost,dc=org base_dn: dc=yunohost,dc=org
argument_auth: true
arguments: arguments:
-v: -v:
full: --version full: --version
@ -67,6 +68,8 @@ user:
list: list:
action_help: List users action_help: List users
api: GET /users api: GET /users
configuration:
authenticate: all
arguments: arguments:
--fields: --fields:
help: fields to fetch help: fields to fetch
@ -77,14 +80,18 @@ user:
-l: -l:
full: --limit full: --limit
help: Maximum number of user fetched help: Maximum number of user fetched
type: int
-o: -o:
full: --offset full: --offset
help: Starting number for user fetching help: Starting number for user fetching
type: int
### user_create() ### user_create()
create: create:
action_help: Create user action_help: Create user
api: POST /users api: POST /users
configuration:
authenticate: all
arguments: arguments:
-u: -u:
full: --username full: --username
@ -119,6 +126,8 @@ user:
delete: delete:
action_help: Delete user action_help: Delete user
api: 'DELETE /users/<users>' api: 'DELETE /users/<users>'
configuration:
authenticate: all
arguments: arguments:
-u: -u:
full: --users full: --users
@ -136,6 +145,8 @@ user:
update: update:
action_help: Update user informations action_help: Update user informations
api: 'PUT /users/<username>' api: 'PUT /users/<username>'
configuration:
authenticate: all
arguments: arguments:
username: username:
help: Username of user to update help: Username of user to update
@ -170,6 +181,8 @@ user:
info: info:
action_help: Get user informations action_help: Get user informations
api: 'GET /users/<username>' api: 'GET /users/<username>'
configuration:
authenticate: all
arguments: arguments:
username: username:
help: Username or mail to get informations help: Username or mail to get informations

View file

@ -2,7 +2,7 @@
""" License """ License
Copyright (C) 2013 YunoHost Copyright (C) 2014 YUNOHOST.ORG
This program is free software; you can redistribute it and/or modify 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 it under the terms of the GNU Affero General Public License as published
@ -23,22 +23,18 @@
Manage users Manage users
""" """
import logging
logging.warning('the module yunohost.user has not been revisited and updated yet')
import os import os
import sys import sys
import ldap
import crypt import crypt
import random import random
import string import string
import getpass from moulinette.core import MoulinetteError
from domain import domain_list
from hook import hook_callback
from moulinette.helpers import YunoHostError, YunoHostLDAP, win_msg, colorize, validate, get_required_args from yunohost.domain import domain_list
from yunohost.hook import hook_callback
def user_list(fields=None, filter=None, limit=None, offset=None):
def user_list(auth, fields=None, filter=None, limit=None, offset=None):
""" """
List users List users
@ -49,47 +45,44 @@ def user_list(fields=None, filter=None, limit=None, offset=None):
fields -- fields to fetch fields -- fields to fetch
""" """
with YunoHostLDAP() as yldap: user_attrs = { 'uid': 'username',
user_attrs = ['uid', 'mail', 'cn', 'maildrop'] 'cn': 'fullname',
attrs = [] 'mail': 'mail',
result_list = [] 'maildrop': 'mail-forward' }
if offset: offset = int(offset) attrs = []
else: offset = 0 result_list = []
if limit: limit = int(limit)
else: limit = 1000
if not filter: filter = 'uid=*'
if fields:
for attr in fields.items():
if attr in user_attrs:
attrs.append(attr)
continue
else:
raise YunoHostError(22, _("Invalid field : ") + attr)
else:
attrs = user_attrs
result = yldap.search('ou=users,dc=yunohost,dc=org', filter, attrs) # Set default arguments values
if offset is None:
offset = 0
if limit is None:
limit = 1000
if filter is None:
filter = '(&(objectclass=person)(!(uid=root))(!(uid=nobody)))'
if fields:
for attr in user_attrs.keys():
if attr in fields:
attrs.append(attr)
else:
raise MoulinetteError(22, _("Invalid field '%s'") % attr)
else:
attrs = [ 'uid', 'cn', 'mail' ]
if result and len(result) > (0 + offset) and limit > 0: result = auth.search('ou=users,dc=yunohost,dc=org', filter, attrs)
i = 0 + offset
for user in result[i:]:
if i - offset < limit:
if user['uid'][0] == 'root' or user['uid'][0] == 'nobody':
continue
entry = {
'Username': user['uid'][0],
'Fullname': user['cn'][0],
}
if 'mail' in user.keys():
entry['Mail'] = user['mail'][0]
result_list.append(entry) if len(result) > offset and limit > 0:
i += 1 for user in result[offset:offset+limit]:
entry = {}
return { 'Users' : result_list } for attr, values in user.items():
try:
entry[user_attrs[attr]] = values[0]
except:
pass
result_list.append(entry)
return { 'users' : result_list }
def user_create(username, firstname, lastname, mail, password): def user_create(auth, username, firstname, lastname, mail, password):
""" """
Create user Create user
@ -101,64 +94,63 @@ def user_create(username, firstname, lastname, mail, password):
password password
""" """
with YunoHostLDAP() as yldap: # Validate password length
# Validate password length if len(password) < 4:
if len(password) < 4: raise MoulinetteError(22, _("Password is too short"))
raise YunoHostError(22, _("Password is too short"))
yldap.validate_uniqueness({ auth.validate_uniqueness({
'uid' : username, 'uid' : username,
'mail' : mail 'mail' : mail
}) })
if mail[mail.find('@')+1:] not in domain_list()['Domains']: if mail[mail.find('@')+1:] not in domain_list()['Domains']:
raise YunoHostError(22, _("Domain not found : ")+ mail[mail.find('@')+1:]) raise MoulinetteError(22, _("Unknown domain '%s'") % mail[mail.find('@')+1:])
# Get random UID/GID # Get random UID/GID
uid_check = gid_check = 0 uid_check = gid_check = 0
while uid_check == 0 and gid_check == 0: while uid_check == 0 and gid_check == 0:
uid = str(random.randint(200, 99999)) uid = str(random.randint(200, 99999))
uid_check = os.system("getent passwd " + uid) uid_check = os.system("getent passwd " + uid)
gid_check = os.system("getent group " + uid) gid_check = os.system("getent group " + uid)
# Adapt values for LDAP # Adapt values for LDAP
fullname = firstname + ' ' + lastname fullname = firstname + ' ' + lastname
rdn = 'uid=' + username + ',ou=users' rdn = 'uid=' + username + ',ou=users'
char_set = string.ascii_uppercase + string.digits char_set = string.ascii_uppercase + string.digits
salt = ''.join(random.sample(char_set,8)) salt = ''.join(random.sample(char_set,8))
salt = '$1$' + salt + '$' salt = '$1$' + salt + '$'
pwd = '{CRYPT}' + crypt.crypt(str(password), salt) pwd = '{CRYPT}' + crypt.crypt(str(password), salt)
attr_dict = { attr_dict = {
'objectClass' : ['mailAccount', 'inetOrgPerson', 'posixAccount'], 'objectClass' : ['mailAccount', 'inetOrgPerson', 'posixAccount'],
'givenName' : firstname, 'givenName' : firstname,
'sn' : lastname, 'sn' : lastname,
'displayName' : fullname, 'displayName' : fullname,
'cn' : fullname, 'cn' : fullname,
'uid' : username, 'uid' : username,
'mail' : mail, 'mail' : mail,
'maildrop' : username, 'maildrop' : username,
'userPassword' : pwd, 'userPassword' : pwd,
'gidNumber' : uid, 'gidNumber' : uid,
'uidNumber' : uid, 'uidNumber' : uid,
'homeDirectory' : '/home/' + username, 'homeDirectory' : '/home/' + username,
'loginShell' : '/bin/false' 'loginShell' : '/bin/false'
} }
if yldap.add(rdn, attr_dict): if auth.add(rdn, attr_dict):
os.system("su - " + username + " -c ''") os.system("su - " + username + " -c ''")
os.system('yunohost app ssowatconf > /dev/null 2>&1') os.system('yunohost app ssowatconf > /dev/null 2>&1')
#TODO: Send a welcome mail to user #TODO: Send a welcome mail to user
win_msg(_("User successfully created")) msignals.display(_("User '%s' successfully created.") % username, 'success')
hook_callback('post_user_create', [username, mail, password, firstname, lastname]) hook_callback('post_user_create', [username, mail, password, firstname, lastname])
return { _("Fullname") : fullname, _("Username") : username, _("Mail") : mail } return { 'fullname' : fullname, 'username' : username, 'mail' : mail }
else: else:
raise YunoHostError(169, _("An error occured during user creation")) raise MoulinetteError(169, _("An error occurred during user creation"))
def user_delete(users, purge=False): def user_delete(auth, users, purge=False):
""" """
Delete user Delete user
@ -167,27 +159,25 @@ def user_delete(users, purge=False):
purge purge
""" """
with YunoHostLDAP() as yldap: if not isinstance(users, list):
result = { 'Users' : [] } users = [ users ]
deleted = []
if not isinstance(users, list): for user in users:
users = [ users ] if auth.remove('uid=' + user + ',ou=users'):
if purge:
os.system('rm -rf /home/' + user)
deleted.append(user)
continue
else:
raise MoulinetteError(169, _("An error occurred during user deletion"))
for user in users: os.system('yunohost app ssowatconf > /dev/null 2>&1')
if yldap.remove('uid=' + user+ ',ou=users'): msignals.display(_("User(s) successfully deleted."), 'success')
if purge: return { 'users': deleted }
os.system('rm -rf /home/' + user)
result['Users'].append(user)
continue
else:
raise YunoHostError(169, _("An error occured during user deletion"))
os.system('yunohost app ssowatconf > /dev/null 2>&1')
win_msg(_("User(s) successfully deleted"))
return result
def user_update(username, firstname=None, lastname=None, mail=None, change_password=None, add_mailforward=None, remove_mailforward=None, add_mailalias=None, remove_mailalias=None): def user_update(auth, username, firstname=None, lastname=None, mail=None, change_password=None, add_mailforward=None, remove_mailforward=None, add_mailalias=None, remove_mailalias=None):
""" """
Update user informations Update user informations
@ -203,90 +193,88 @@ def user_update(username, firstname=None, lastname=None, mail=None, change_passw
remove_mailalias -- Mail aliases to remove remove_mailalias -- Mail aliases to remove
""" """
with YunoHostLDAP() as yldap: attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop'] new_attr_dict = {}
new_attr_dict = {} domains = domain_list()['Domains']
domains = domain_list()['Domains']
# Populate user informations # Populate user informations
result = yldap.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch) result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch)
if not result: if not result:
raise YunoHostError(167, _("No user found")) raise MoulinetteError(167, _("Unknown username '%s'") % username)
user = result[0] user = result[0]
# Get modifications from arguments # Get modifications from arguments
if firstname: if firstname:
new_attr_dict['givenName'] = firstname # TODO: Validate new_attr_dict['givenName'] = firstname # TODO: Validate
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + user['sn'][0] new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + user['sn'][0]
if lastname: if lastname:
new_attr_dict['sn'] = lastname # TODO: Validate new_attr_dict['sn'] = lastname # TODO: Validate
new_attr_dict['cn'] = new_attr_dict['displayName'] = user['givenName'][0] + ' ' + lastname new_attr_dict['cn'] = new_attr_dict['displayName'] = user['givenName'][0] + ' ' + lastname
if lastname and firstname: if lastname and firstname:
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname
if change_password: if change_password:
char_set = string.ascii_uppercase + string.digits char_set = string.ascii_uppercase + string.digits
salt = ''.join(random.sample(char_set,8)) salt = ''.join(random.sample(char_set,8))
salt = '$1$' + salt + '$' salt = '$1$' + salt + '$'
new_attr_dict['userPassword'] = '{CRYPT}' + crypt.crypt(str(change_password), salt) new_attr_dict['userPassword'] = '{CRYPT}' + crypt.crypt(str(change_password), salt)
if mail: if mail:
yldap.validate_uniqueness({ 'mail': mail }) auth.validate_uniqueness({ 'mail': mail })
if mail[mail.find('@')+1:] not in domains:
raise MoulinetteError(22, _("Unknown domain '%s'") % mail[mail.find('@')+1:])
del user['mail'][0]
new_attr_dict['mail'] = [mail] + user['mail']
if add_mailalias:
if not isinstance(add_mailalias, list):
add_mailalias = [ add_mailalias ]
for mail in add_mailalias:
auth.validate_uniqueness({ 'mail': mail })
if mail[mail.find('@')+1:] not in domains: if mail[mail.find('@')+1:] not in domains:
raise YunoHostError(22, _("Domain not found : ")+ mail[mail.find('@')+1:]) raise MoulinetteError(22, _("Unknown domain '%s'") % mail[mail.find('@')+1:])
del user['mail'][0] user['mail'].append(mail)
new_attr_dict['mail'] = [mail] + user['mail'] new_attr_dict['mail'] = user['mail']
if add_mailalias: if remove_mailalias:
if not isinstance(add_mailalias, list): if not isinstance(remove_mailalias, list):
add_mailalias = [ add_mailalias ] remove_mailalias = [ remove_mailalias ]
for mail in add_mailalias: for mail in remove_mailalias:
yldap.validate_uniqueness({ 'mail': mail }) if len(user['mail']) > 1 and mail in user['mail'][1:]:
if mail[mail.find('@')+1:] not in domains: user['mail'].remove(mail)
raise YunoHostError(22, _("Domain not found : ")+ mail[mail.find('@')+1:]) else:
user['mail'].append(mail) raise MoulinetteError(22, _("Invalid mail alias '%s'") % mail)
new_attr_dict['mail'] = user['mail'] new_attr_dict['mail'] = user['mail']
if remove_mailalias: if add_mailforward:
if not isinstance(remove_mailalias, list): if not isinstance(add_mailforward, list):
remove_mailalias = [ remove_mailalias ] add_mailforward = [ add_mailforward ]
for mail in remove_mailalias: for mail in add_mailforward:
if len(user['mail']) > 1 and mail in user['mail'][1:]: if mail in user['maildrop'][1:]:
user['mail'].remove(mail) continue
else: user['maildrop'].append(mail)
raise YunoHostError(22, _("Invalid mail alias : ") + mail) new_attr_dict['maildrop'] = user['maildrop']
new_attr_dict['mail'] = user['mail']
if add_mailforward: if remove_mailforward:
if not isinstance(add_mailforward, list): if not isinstance(remove_mailforward, list):
add_mailforward = [ add_mailforward ] remove_mailforward = [ remove_mailforward ]
for mail in add_mailforward: for mail in remove_mailforward:
if mail in user['maildrop'][1:]: if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]:
continue user['maildrop'].remove(mail)
user['maildrop'].append(mail) else:
new_attr_dict['maildrop'] = user['maildrop'] raise MoulinetteError(22, _("Invalid mail forward '%s'") % mail)
new_attr_dict['maildrop'] = user['maildrop']
if remove_mailforward: if auth.update('uid=' + username + ',ou=users', new_attr_dict):
if not isinstance(remove_mailforward, list): msignals.display(_("User '%s' successfully updated.") % username, 'success')
remove_mailforward = [ remove_mailforward ] return user_info(username)
for mail in remove_mailforward: else:
if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]: raise MoulinetteError(169, _("An error occurred during user update"))
user['maildrop'].remove(mail)
else:
raise YunoHostError(22, _("Invalid mail forward : ") + mail)
new_attr_dict['maildrop'] = user['maildrop']
if yldap.update('uid=' + username + ',ou=users', new_attr_dict):
win_msg(_("User successfully updated"))
return user_info(username)
else:
raise YunoHostError(169, _("An error occured during user update"))
def user_info(auth, username):
def user_info(username):
""" """
Get user informations Get user informations
@ -294,37 +282,35 @@ def user_info(username):
username -- Username or mail to get informations username -- Username or mail to get informations
""" """
with YunoHostLDAP() as yldap: user_attrs = ['cn', 'mail', 'uid', 'maildrop', 'givenName', 'sn']
user_attrs = ['cn', 'mail', 'uid', 'maildrop', 'givenName', 'sn']
if len(username.split('@')) is 2: if len(username.split('@')) is 2:
filter = 'mail='+ username filter = 'mail='+ username
else: else:
filter = 'uid='+ username filter = 'uid='+ username
result = yldap.search('ou=users,dc=yunohost,dc=org', filter, user_attrs) result = auth.search('ou=users,dc=yunohost,dc=org', filter, user_attrs)
if result: if result:
user = result[0] user = result[0]
else: else:
raise YunoHostError(22, _("Unknown user/mail : ") + username) raise MoulinetteError(22, _("Unknown username/mail '%s'") % username)
result_dict = { result_dict = {
'Username': user['uid'][0], 'username': user['uid'][0],
'Fullname': user['cn'][0], 'fullname': user['cn'][0],
'Firstname': user['givenName'][0], 'firstname': user['givenName'][0],
'Lastname': user['sn'][0], 'lastname': user['sn'][0],
'Mail': user['mail'][0] 'mail': user['mail'][0]
} }
if len(user['mail']) > 1: if len(user['mail']) > 1:
result_dict['Mail Aliases'] = user['mail'][1:] result_dict['mail-aliases'] = user['mail'][1:]
if len(user['maildrop']) > 1: if len(user['maildrop']) > 1:
result_dict['Mail Forward'] = user['maildrop'][1:] result_dict['mail-forward'] = user['maildrop'][1:]
if result:
return result_dict
else:
raise YunoHostError(167, _("No user found"))
if result:
return result_dict
else:
raise MoulinetteError(167, _("No user found"))