Fixes for user groups

- Edit user form fails because a 'wiki' group entry exists in the database
  though the ikiwiki app hasn't been installed yet.
- Register group when a user group is created by an application, so that a
  plinth restart can be avoided.

Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Joseph Nuthalapati 2017-11-23 12:59:57 +05:30 committed by James Valleroy
parent 7ce5d1f636
commit 32b2ef38c7
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
3 changed files with 121 additions and 145 deletions

View File

@ -21,11 +21,11 @@ Configuration helper for the LDAP user directory
""" """
import argparse import argparse
import augeas
import re import re
import subprocess import subprocess
import sys import sys
import augeas
from plinth import action_utils from plinth import action_utils
ACCESS_CONF = '/etc/security/access.conf' ACCESS_CONF = '/etc/security/access.conf'
@ -39,44 +39,41 @@ def parse_arguments():
subparsers.add_parser('setup', help='Setup LDAP') subparsers.add_parser('setup', help='Setup LDAP')
subparser = subparsers.add_parser( subparser = subparsers.add_parser('create-user',
'create-user', help='Create an LDAP user') help='Create an LDAP user')
subparser.add_argument('username', help='Name of the LDAP user to create') subparser.add_argument('username', help='Name of the LDAP user to create')
subparser = subparsers.add_parser( subparser = subparsers.add_parser('remove-user',
'remove-user', help='Delete an LDAP user') help='Delete an LDAP user')
subparser.add_argument('username', help='Name of the LDAP user to delete') subparser.add_argument('username', help='Name of the LDAP user to delete')
subparser = subparsers.add_parser( subparser = subparsers.add_parser('rename-user',
'rename-user', help='Rename an LDAP user') help='Rename an LDAP user')
subparser.add_argument('oldusername', help='Old name of the LDAP user') subparser.add_argument('oldusername', help='Old name of the LDAP user')
subparser.add_argument('newusername', help='New name of the LDAP user') subparser.add_argument('newusername', help='New name of the LDAP user')
subparser = subparsers.add_parser( subparser = subparsers.add_parser('set-user-password',
'set-user-password', help='Set the password of an LDAP user') help='Set the password of an LDAP user')
subparser.add_argument( subparser.add_argument(
'username', help='Name of the LDAP user to set the password for') 'username', help='Name of the LDAP user to set the password for')
subparser = subparsers.add_parser( subparser = subparsers.add_parser('create-group',
'create-group', help='Create an LDAP group') help='Create an LDAP group')
subparser.add_argument( subparser.add_argument('groupname',
'groupname', help='Name of the LDAP group to create') help='Name of the LDAP group to create')
subparser = subparsers.add_parser( subparser = subparsers.add_parser('remove-group',
'remove-group', help='Delete an LDAP group') help='Delete an LDAP group')
subparser.add_argument( subparser.add_argument('groupname',
'groupname', help='Name of the LDAP group to delete') help='Name of the LDAP group to delete')
subparser = subparsers.add_parser( subparser = subparsers.add_parser(
'get-user-groups', help='Get all the LDAP groups for an LDAP user') 'get-user-groups', help='Get all the LDAP groups for an LDAP user')
subparser.add_argument( subparser.add_argument('username',
'username', help='LDAP user to retrieve the groups for') help='LDAP user to retrieve the groups for')
subparser = subparsers.add_parser( subparser = subparsers.add_parser('add-user-to-group',
'get-all-groups', help='Get a list of all the LDAP groups in the system') help='Add an LDAP user to an LDAP group')
subparser = subparsers.add_parser(
'add-user-to-group', help='Add an LDAP user to an LDAP group')
subparser.add_argument('username', help='LDAP user to add to group') subparser.add_argument('username', help='LDAP user to add to group')
subparser.add_argument('groupname', help='LDAP group to add the user to') subparser.add_argument('groupname', help='LDAP group to add the user to')
@ -84,8 +81,8 @@ def parse_arguments():
'remove-user-from-group', 'remove-user-from-group',
help='Remove an LDAP user from an LDAP group') help='Remove an LDAP user from an LDAP group')
subparser.add_argument('username', help='LDAP user to remove from group') subparser.add_argument('username', help='LDAP user to remove from group')
subparser.add_argument( subparser.add_argument('groupname',
'groupname', help='LDAP group to remove the user from') help='LDAP group to remove the user from')
subparsers.required = True subparsers.required = True
return parser.parse_args() return parser.parse_args()
@ -138,13 +135,10 @@ def create_organizational_unit(unit):
"""Create an organizational unit in LDAP.""" """Create an organizational unit in LDAP."""
distinguished_name = 'ou={unit},dc=thisbox'.format(unit=unit) distinguished_name = 'ou={unit},dc=thisbox'.format(unit=unit)
try: try:
subprocess.run( subprocess.run([
[ 'ldapsearch', '-Q', '-Y', 'EXTERNAL', '-H', 'ldapi:///', '-s',
'ldapsearch', '-Q', '-Y', 'EXTERNAL', '-H', 'ldapi:///', '-s', 'base', '-b', distinguished_name, '(objectclass=*)'
'base', '-b', distinguished_name, '(objectclass=*)' ], stdout=subprocess.DEVNULL, check=True)
],
stdout=subprocess.DEVNULL,
check=True)
return # Already exists return # Already exists
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
input = ''' input = '''
@ -152,23 +146,18 @@ dn: ou={unit},dc=thisbox
objectClass: top objectClass: top
objectClass: organizationalUnit objectClass: organizationalUnit
ou: {unit}'''.format(unit=unit) ou: {unit}'''.format(unit=unit)
subprocess.run( subprocess.run(['ldapadd', '-Q', '-Y', 'EXTERNAL', '-H',
['ldapadd', '-Q', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], 'ldapi:///'], input=input.encode(),
input=input.encode(), stdout=subprocess.DEVNULL, check=True)
stdout=subprocess.DEVNULL,
check=True)
def setup_admin(): def setup_admin():
"""Remove LDAP admin password and Allow root to modify the users.""" """Remove LDAP admin password and Allow root to modify the users."""
process = subprocess.run( process = subprocess.run([
[ 'ldapsearch', '-Q', '-L', '-L', '-L', '-Y', 'EXTERNAL', '-H',
'ldapsearch', '-Q', '-L', '-L', '-L', '-Y', 'EXTERNAL', '-H', 'ldapi:///', '-s', 'base', '-b', 'olcDatabase={1}mdb,cn=config',
'ldapi:///', '-s', 'base', '-b', 'olcDatabase={1}mdb,cn=config', '(objectclass=*)', 'olcRootDN', 'olcRootPW'
'(objectclass=*)', 'olcRootDN', 'olcRootPW' ], check=True, stdout=subprocess.PIPE)
],
check=True,
stdout=subprocess.PIPE)
ldap_object = {} ldap_object = {}
for line in process.stdout.decode().splitlines(): for line in process.stdout.decode().splitlines():
if line: if line:
@ -177,10 +166,8 @@ def setup_admin():
if 'olcRootPW' in ldap_object: if 'olcRootPW' in ldap_object:
subprocess.run( subprocess.run(
['ldapmodify', '-Q', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ['ldapmodify', '-Q', '-Y', 'EXTERNAL', '-H',
check=True, 'ldapi:///'], check=True, stdout=subprocess.DEVNULL, input=b'''
stdout=subprocess.DEVNULL,
input=b'''
dn: olcDatabase={1}mdb,cn=config dn: olcDatabase={1}mdb,cn=config
changetype: modify changetype: modify
delete: olcRootPW''') delete: olcRootPW''')
@ -188,10 +175,8 @@ delete: olcRootPW''')
root_dn = 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth' root_dn = 'gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth'
if ldap_object['olcRootDN'] != root_dn: if ldap_object['olcRootDN'] != root_dn:
subprocess.run( subprocess.run(
['ldapmodify', '-Q', '-Y', 'EXTERNAL', '-H', 'ldapi:///'], ['ldapmodify', '-Q', '-Y', 'EXTERNAL', '-H',
check=True, 'ldapi:///'], check=True, stdout=subprocess.DEVNULL, input=b'''
stdout=subprocess.DEVNULL,
input=b'''
dn: olcDatabase={1}mdb,cn=config dn: olcDatabase={1}mdb,cn=config
changetype: modify changetype: modify
replace: olcRootDN replace: olcRootDN
@ -261,8 +246,7 @@ def subcommand_rename_user(arguments):
def set_user_password(username, password): def set_user_password(username, password):
"""Set a user's password.""" """Set a user's password."""
process = _run( process = _run(['slappasswd', '-s', password], stdout=subprocess.PIPE)
['slappasswd', '-s', password], stdout=subprocess.PIPE)
password = process.stdout.decode().strip() password = process.stdout.decode().strip()
_run(['ldapsetpasswd', username, password]) _run(['ldapsetpasswd', username, password])
@ -276,14 +260,14 @@ def get_user_groups(username):
"""Returns only the supplementary groups of the given user. """Returns only the supplementary groups of the given user.
Exclude the 'users' primary group from the returned list.""" Exclude the 'users' primary group from the returned list."""
process = _run( process = _run(['ldapid', username], stdout=subprocess.PIPE, check=False)
['ldapid', username], stdout=subprocess.PIPE, check=False)
output = process.stdout.decode().strip() output = process.stdout.decode().strip()
if output: if output:
groups_part = output.split(' ')[2] groups_part = output.split(' ')[2]
groups = groups_part.split('=')[1] groups = groups_part.split('=')[1]
group_names = [user.strip('()') group_names = [
for user in re.findall('\(.*?\)', groups)] user.strip('()') for user in re.findall('\(.*?\)', groups)
]
group_names.remove('users') group_names.remove('users')
return group_names return group_names

View File

@ -14,26 +14,25 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
""" """
Plinth module to manage users Plinth module to manage users
""" """
from django.utils.translation import ugettext_lazy as _
import subprocess import subprocess
from plinth import action_utils from django.utils.translation import ugettext_lazy as _
from plinth import actions
from plinth.errors import ActionError
from plinth.menu import main_menu
from plinth import action_utils, actions
from plinth.menu import main_menu
version = 1 version = 1
is_essential = True is_essential = True
managed_packages = ['ldapscripts', 'ldap-utils', 'libnss-ldapd', managed_packages = [
'libpam-ldapd', 'nslcd', 'slapd'] 'ldapscripts', 'ldap-utils', 'libnss-ldapd', 'libpam-ldapd', 'nslcd',
'slapd'
]
first_boot_steps = [ first_boot_steps = [
{ {
@ -80,19 +79,22 @@ def _diagnose_ldap_entry(search_item):
result = 'failed' result = 'failed'
try: try:
subprocess.check_output(['ldapsearch', '-x', '-b', 'dc=thisbox', subprocess.check_output(
search_item]) ['ldapsearch', '-x', '-b', 'dc=thisbox', search_item])
result = 'passed' result = 'passed'
except subprocess.CalledProcessError: except subprocess.CalledProcessError:
pass pass
return [_('Check LDAP entry "{search_item}"') return [
.format(search_item=search_item), result] _('Check LDAP entry "{search_item}"').format(search_item=search_item),
result
]
def create_group(group): def create_group(group):
"""Add an LDAP group.""" """Add an LDAP group."""
actions.superuser_run('users', options=['create-group', group]) actions.superuser_run('users', options=['create-group', group])
register_group(group)
def remove_group(group): def remove_group(group):
@ -100,14 +102,5 @@ def remove_group(group):
actions.superuser_run('users', options=['remove-group', group]) actions.superuser_run('users', options=['remove-group', group])
def get_all_groups():
"""Retrieve the set of all LDAP groups in the system"""
try:
groups = actions.superuser_run('users', options=['get-all-groups'])
return set(groups.strip().split())
except ActionError:
return {}
def register_group(group): def register_group(group):
groups.add(group) groups.add(group)

View File

@ -18,20 +18,18 @@
import subprocess import subprocess
from django import forms from django import forms
from django.contrib import auth from django.contrib import auth, messages
from django.contrib import messages from django.contrib.auth.forms import SetPasswordForm, UserCreationForm
from django.contrib.auth.models import User, Group from django.contrib.auth.models import Group, User
from django.contrib.auth.forms import UserCreationForm, SetPasswordForm
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from plinth import actions from plinth import actions, module_loader
from plinth.errors import ActionError from plinth.errors import ActionError
from plinth.modules import first_boot from plinth.modules import first_boot, users
from plinth.modules import users
from plinth.modules.security import set_restricted_access from plinth.modules.security import set_restricted_access
from plinth.utils import is_user_admin from plinth.utils import is_user_admin
from plinth import module_loader
def get_group_choices(): def get_group_choices():
@ -50,8 +48,8 @@ class ValidNewUsernameCheckMixin(object):
username = self.cleaned_data['username'] username = self.cleaned_data['username']
if self.instance.username != username and \ if self.instance.username != username and \
not self.is_valid_new_username(): not self.is_valid_new_username():
raise ValidationError(_('Username is taken or is reserved.'), raise ValidationError(
code='invalid') _('Username is taken or is reserved.'), code='invalid')
return username return username
@ -80,11 +78,9 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm):
Include options to add user to groups. Include options to add user to groups.
""" """
groups = forms.MultipleChoiceField( groups = forms.MultipleChoiceField(
choices=get_group_choices(), choices=get_group_choices(), label=ugettext_lazy('Permissions'),
label=ugettext_lazy('Permissions'),
required=False, required=False,
widget=forms.CheckboxSelectMultiple, widget=forms.CheckboxSelectMultiple, help_text=ugettext_lazy(
help_text=ugettext_lazy(
'Select which services should be available to the new ' 'Select which services should be available to the new '
'user. The user will be able to log in to services that ' 'user. The user will be able to log in to services that '
'support single sign-on through LDAP, if they are in the ' 'support single sign-on through LDAP, if they are in the '
@ -105,24 +101,23 @@ class CreateUserForm(ValidNewUsernameCheckMixin, UserCreationForm):
if commit: if commit:
try: try:
actions.superuser_run( actions.superuser_run('users', [
'users', 'create-user', user.get_username()
['create-user', user.get_username()], ], input=self.cleaned_data['password1'].encode())
input=self.cleaned_data['password1'].encode())
except ActionError: except ActionError:
messages.error(self.request, messages.error(self.request, _('Creating LDAP user failed.'))
_('Creating LDAP user failed.'))
for group in self.cleaned_data['groups']: for group in self.cleaned_data['groups']:
try: try:
actions.superuser_run( actions.superuser_run('users', [
'users', 'add-user-to-group',
['add-user-to-group', user.get_username(), group]) user.get_username(), group
])
except ActionError: except ActionError:
messages.error( messages.error(
self.request, self.request,
_('Failed to add new user to {group} group.') _('Failed to add new user to {group} group.').format(
.format(group=group)) group=group))
group_object, created = Group.objects.get_or_create(name=group) group_object, created = Group.objects.get_or_create(name=group)
group_object.user_set.add(user) group_object.user_set.add(user)
@ -134,9 +129,7 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm):
"""When user info is changed, also updates LDAP user.""" """When user info is changed, also updates LDAP user."""
ssh_keys = forms.CharField( ssh_keys = forms.CharField(
label=ugettext_lazy('SSH Keys'), label=ugettext_lazy('SSH Keys'),
required=False, required=False, widget=forms.Textarea, help_text=ugettext_lazy(
widget=forms.Textarea,
help_text=ugettext_lazy(
'Setting an SSH public key will allow this user to ' 'Setting an SSH public key will allow this user to '
'securely log in to the system without using a ' 'securely log in to the system without using a '
'password. You may enter multiple keys, one on each ' 'password. You may enter multiple keys, one on each '
@ -161,9 +154,14 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm):
self.username = username self.username = username
super(UserUpdateForm, self).__init__(*args, **kwargs) super(UserUpdateForm, self).__init__(*args, **kwargs)
choices = [ # Replace group names with descriptions choices = []
(c[0], group_choices[c[1]])
for c in sorted(self.fields['groups'].choices, key=lambda x: x[1])] for c in sorted(self.fields['groups'].choices, key=lambda x: x[1]):
# Handle case where groups exist in database for
# applications not installed yet.
if c[1] in group_choices:
# Replace group names with descriptions
choices.append((c[0], group_choices[c[1]]))
self.fields['groups'].label = 'Permissions' self.fields['groups'].label = 'Permissions'
self.fields['groups'].choices = choices self.fields['groups'].choices = choices
@ -177,16 +175,17 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm):
user = super(UserUpdateForm, self).save(commit) user = super(UserUpdateForm, self).save(commit)
if commit: if commit:
output = actions.superuser_run( output = actions.superuser_run('users',
'users', ['get-user-groups', self.username]) ['get-user-groups', self.username])
old_groups = output.strip().split('\n') old_groups = output.strip().split('\n')
old_groups = [group for group in old_groups if group] old_groups = [group for group in old_groups if group]
if self.username != user.get_username(): if self.username != user.get_username():
try: try:
actions.superuser_run( actions.superuser_run('users', [
'users', 'rename-user', self.username,
['rename-user', self.username, user.get_username()]) user.get_username()
])
except ActionError: except ActionError:
messages.error(self.request, messages.error(self.request,
_('Renaming LDAP user failed.')) _('Renaming LDAP user failed.'))
@ -195,10 +194,10 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm):
for old_group in old_groups: for old_group in old_groups:
if old_group not in new_groups: if old_group not in new_groups:
try: try:
actions.superuser_run( actions.superuser_run('users', [
'users', 'remove-user-from-group',
['remove-user-from-group', user.get_username(), user.get_username(), old_group
old_group]) ])
except ActionError: except ActionError:
messages.error(self.request, messages.error(self.request,
_('Failed to remove user from group.')) _('Failed to remove user from group.'))
@ -206,18 +205,20 @@ class UserUpdateForm(ValidNewUsernameCheckMixin, forms.ModelForm):
for new_group in new_groups: for new_group in new_groups:
if new_group not in old_groups: if new_group not in old_groups:
try: try:
actions.superuser_run( actions.superuser_run('users', [
'users', 'add-user-to-group',
['add-user-to-group', user.get_username(), user.get_username(), new_group
new_group]) ])
except ActionError: except ActionError:
messages.error(self.request, messages.error(self.request,
_('Failed to add user to group.')) _('Failed to add user to group.'))
try: try:
actions.superuser_run( actions.superuser_run('ssh', [
'ssh', ['set-keys', '--username', user.get_username(), 'set-keys', '--username',
'--keys', self.cleaned_data['ssh_keys'].strip()]) user.get_username(), '--keys',
self.cleaned_data['ssh_keys'].strip()
])
except ActionError: except ActionError:
messages.error(self.request, _('Unable to set SSH keys.')) messages.error(self.request, _('Unable to set SSH keys.'))
@ -237,14 +238,13 @@ class UserChangePasswordForm(SetPasswordForm):
user = super(UserChangePasswordForm, self).save(commit) user = super(UserChangePasswordForm, self).save(commit)
if commit: if commit:
try: try:
actions.superuser_run( actions.superuser_run('users', [
'users', 'set-user-password',
['set-user-password', user.get_username()], user.get_username()
input=self.cleaned_data['new_password1'].encode()) ], input=self.cleaned_data['new_password1'].encode())
except ActionError: except ActionError:
messages.error( messages.error(self.request,
self.request, _('Changing LDAP user password failed.'))
_('Changing LDAP user password failed.'))
return user return user
@ -263,18 +263,17 @@ class FirstBootForm(ValidNewUsernameCheckMixin, auth.forms.UserCreationForm):
first_boot.mark_step_done('users_firstboot') first_boot.mark_step_done('users_firstboot')
try: try:
actions.superuser_run( actions.superuser_run('users', [
'users', 'create-user', user.get_username()
['create-user', user.get_username()], ], input=self.cleaned_data['password1'].encode())
input=self.cleaned_data['password1'].encode())
except ActionError: except ActionError:
messages.error(self.request, messages.error(self.request, _('Creating LDAP user failed.'))
_('Creating LDAP user failed.'))
try: try:
actions.superuser_run( actions.superuser_run('users', [
'users', 'add-user-to-group',
['add-user-to-group', user.get_username(), 'admin']) user.get_username(), 'admin'
])
except ActionError: except ActionError:
messages.error(self.request, messages.error(self.request,
_('Failed to add new user to admin group.')) _('Failed to add new user to admin group.'))