More Inside LDAP sync work and cleans, disables some krb5 and automount stuff

This commit is contained in:
nikolark committed 2015-03-01 15:35:55 +01:00
1 parent d24f22b4a4
commit f963f11c88
10 files changed
+212 -103

No files matched your search

+4 -7
View File
@@ -46,14 +46,11 @@ class InsideBackend(object):
return self.user
def _update_user_details(self):
sync_attrs = {
'firstname': 'first_name',
'lastname': 'last_name',
'email': 'email'
}
for inside_attr, local_attr in sync_attrs.iteritems():
sync_attrs = ['first_name', 'last_name', 'email']
for attr in sync_attrs:
# Copy from Inside user to local Django user
setattr(self.user, local_attr, getattr(self.inside_user, inside_attr))
setattr(self.user, attr, getattr(self.inside_user, attr))
def _update_groups(self):
existing_groups = list(self.user.groups.all())
+2 -2
View File
@@ -338,8 +338,8 @@ class InsideUser(models.Model):
cardno = models.IntegerField(unique=True, blank=True, null=True)
username = models.CharField(unique=True, max_length=12)
password = models.CharField(max_length=50)
firstname = models.CharField(max_length=120)
lastname = models.CharField(max_length=120)
first_name = models.CharField(max_length=120, db_column='firstname')
last_name = models.CharField(max_length=120, db_column='lastname')
addresstype = models.CharField(max_length=3)
valid_address = models.IntegerField()
email = models.CharField(unique=True, max_length=120)
+4
View File
@@ -30,6 +30,10 @@ def get_kerberos_principal(username):
return cleaned_output
def has_kerberos_principal(username):
return get_kerberos_principal(username) is not None
def set_kerberos_password(username, raw_password):
principal = "{}@{}".format(username, settings.KERBEROS_REALM)
kadmin_query = " -p {} -w {} -q 'change_password -pw {} {}'".format(
+60 -8
View File
@@ -1,5 +1,6 @@
# coding: utf-8
from __future__ import unicode_literals
import pprint
from django.conf import settings
import logging
import os
@@ -25,6 +26,7 @@ def set_ldap_password(username, raw_password):
user = LdapUser.objects.get(username=username)
user.set_password(raw_password)
except LdapUser.DoesNotExist:
# Ignore
pass
@@ -40,7 +42,7 @@ def ldap_username_exists(username):
return len(LdapUser.objects.filter(username=username)) != 0
def ldap_create_user(user):
def ldap_create_user(user, dry_run=False):
from neuf_ldap.models import LdapUser, LdapGroup
def _get_next_uid():
@@ -70,10 +72,10 @@ def ldap_create_user(user):
return user_groups[0] + 1
# User
full_name = '{} {}'.format(user['firstname'], user['lastname'])
full_name = '{} {}'.format(user['first_name'], user['last_name'])
user_data = {
'first_name': user['firstname'],
'last_name': user['lastname'],
'first_name': user['first_name'],
'last_name': user['last_name'],
'full_name': full_name,
'display_name': full_name,
'gecos': full_name,
@@ -82,26 +84,76 @@ def ldap_create_user(user):
'id': _get_next_uid(),
'group': _get_next_user_gid(),
'home_directory': os.path.join(settings.LDAP_HOME_DIRECTORY_PREFIX, user['username'])
# TODO you are here: AttributeError: 'NoneType' object has no attribute 'startswith'
# $ python manage.py sync_active_to_ldap --dry-run -v3
}
ldap_user = LdapUser(**user_data)
ldap_user.set_password(user['password'], commit=False)
ldap_user.save()
# User password
pwd_type = None
if user.get('password') is not None:
ldap_user.set_password(user['password'], commit=False) # Raw
pwd_type = 'raw'
elif user.get('ldap_password') is not None:
ldap_user.password = user['ldap_password'] # Hashed
pwd_type = 'hashed'
else:
# No password
logger.exception("User {} has no ldap_password (hashed) or password (raw)!".format(user['username']))
if dry_run:
logger.debug('User saved with data: {} and password type \'{}\'.'.format(
pprint.pformat(user_data),
pwd_type))
else:
ldap_user.save()
# Add user group
ldap_user_group = LdapGroup(name=user['username'], gid=user_data['group'], members=[user['username']])
ldap_user_group.save()
if not dry_run:
ldap_user_group.save()
else:
logger.debug('User group {} created'.format(user['username']))
# Add groups
ldap_groups = LdapGroup.objects.filter(name__in=user['groups'])
for g in ldap_groups:
if user['username'] not in g.members:
g.members.append(user['username'])
g.save()
if not dry_run:
g.save()
else:
logger.debug('User {} added to group {}'.format(user['username'], g.name))
# Finito!
return True
def ldap_update_user_details(inside_user, dry_run=False):
from neuf_ldap.models import LdapUser
diff_attributes = ['username', 'email', 'groups']
ldap_user = LdapUser.objects.get(username=inside_user['username'])
for attr in diff_attributes:
setattr(ldap_user, attr, inside_user[attr])
name_changed = inside_user['first_name'] != ldap_user.first_name or inside_user['last_name'] != ldap_user.last_name
if name_changed:
full_name = '{} {}'.format(inside_user['first_name'], inside_user['last_name'])
name_data = {
'first_name': inside_user['first_name'],
'last_name': inside_user['last_name'],
'full_name': full_name,
'display_name': full_name,
'gecos': full_name,
}
for key, value in name_data.iteritems():
setattr(ldap_user, key, value)
if not dry_run:
ldap_user.save()
def ldap_create_automount(username):
from neuf_ldap.models import LdapAutomountHome
automount = LdapAutomountHome(username=username)
+16 -7
View File
@@ -15,12 +15,11 @@ from django.utils.translation import ugettext as _
from inside.models import InsideUser
from inside.utils import set_inside_password
from neuf_kerberos.utils import set_kerberos_password
from neuf_ldap.models import LdapGroup, LdapUser
from neuf_kerberos.utils import set_kerberos_password, has_kerberos_principal
from neuf_radius.utils import set_radius_password
from neuf_ldap.utils import set_ldap_password, ldap_username_exists, ldap_user_group_exists
from neuf_userinfo.utils import decrypt_rijndael
from neuf_userinfo.validators import PasswordValidator, UsernameValidator
from neuf_userinfo.validators import UsernameValidator, validate_password
class NeufSetPasswordForm(SetPasswordForm):
@@ -42,17 +41,19 @@ class NeufSetPasswordForm(SetPasswordForm):
set_inside_password(username, password)
# Active services
set_kerberos_password(username, password)
set_radius_password(username, password)
set_radius_password(username, password) # creates user if non-existant
set_ldap_password(username, password)
if has_kerberos_principal(username):
set_kerberos_password(username, password)
return self.user # Local Django User
def clean_new_password1(self):
raw_password = self.cleaned_data.get('new_password1')
MinLengthValidator(8)(raw_password)
PasswordValidator(raw_password)
validate_password(raw_password)
return raw_password
@@ -144,10 +145,18 @@ class NewUserForm(forms.Form):
def clean_password(self):
raw_password = self.cleaned_data['password']
password = decrypt_rijndael(settings.INSIDE_USERSYNC_ENC_KEY, raw_password)
PasswordValidator(password)
MinLengthValidator(8)(raw_password)
validate_password(password)
return password
def clean_groups(self):
groups = self.cleaned_data['groups']
return groups.strip().split(',')
def clean(self):
cleaned_data = super(NewUserForm, self).clean()
# Rename firstname to first_name and lastname to last_name
cleaned_data['first_name'] = cleaned_data.pop('firstname')
cleaned_data['last_name'] = cleaned_data.pop('lastname')
return cleaned_data
@@ -3,69 +3,51 @@ from django.core.management.base import BaseCommand
from inside.models import InsideGroup, InsideUser
from neuf_ldap.models import LdapGroup, LdapUser
from neuf_ldap.utils import ldap_create_user
from neuf_ldap.utils import ldap_create_user, ldap_update_user_details
from optparse import make_option
from neuf_userinfo.utils import add_new_user
ACTIVE_USERS_GROUP = 'dns-aktiv'
class Command(BaseCommand):
help = 'Syncs users in group dns-aktiv from Inside to LDAP'
option_list = BaseCommand.option_list + (
make_option(
'--dry-run',
action='store_true',
dest='dry_run',
default=False,
help='Dry run of syncronization, nothing is saved.'
),)
DIFF_ATTRIBUTES = ['username', 'first_name', 'last_name', 'email', 'groups']
COUNTS = dict(create=0, update=0, in_sync=0)
options = {}
def handle(self, *args, **options):
self.options = options
# Get all active user from Inside
inside_users_diffable = self.get_inside_users_diffable()
if options['verbosity'] == '3':
self.stdout.write('Found {} Inside users'.format(len(inside_users_diffable)))
# Get existing users in LDAP
ldap_users_diffable = self.get_ldap_users_diffable()
if options['verbosity'] == '3':
self.stdout.write('Found {} LDAP users'.format(len(ldap_users_diffable)))
self.sync_users(inside_users_diffable, ldap_users_diffable, options)
# Do actual sync
self.sync_users(inside_users_diffable, ldap_users_diffable)
self.obsolete_users(inside_users_diffable, ldap_users_diffable, options)
# Are any LDAP users stale?
self.stale_ldap_users(inside_users_diffable, ldap_users_diffable)
self.log_totals(options)
# Voila
self.log_totals()
def sync_users(self, inside_users_diffable, ldap_users_diffable, options):
for username, u in inside_users_diffable.iteritems():
# Diff by username
if username not in ldap_users_diffable:
# TODO create
# ldap_create_user(u)
self.COUNTS['create'] += 1
if int(options['verbosity']) >= 2:
self.stdout.write('Inside user {} is not in LDAP'.format(username))
elif not self.user_details_in_sync(u, ldap_users_diffable[username]):
# TODO update
# ldap_update_user(u)
self.COUNTS['update'] += 1
if int(options['verbosity']) >= 2:
self.stdout.write('Inside user {} is out of sync with LDAP'.format(username))
else:
self.COUNTS['in_sync'] += 1
if int(options['verbosity']) == 3:
self.stdout.write('Inside user {} is in sync with LDAP'.format(username))
def obsolete_users(self, inside_users_diffable, ldap_users_diffable, options):
for username, u in ldap_users_diffable.iteritems():
if username not in inside_users_diffable:
if int(options['verbosity']) >= 2:
self.stdout.write('LDAP user {} is not in Inside'.format(username))
def log_totals(self, options):
if self.COUNTS['create'] != 0:
self.stdout.write('{} Inside users not found in LDAP'.format(self.COUNTS['create']))
if self.COUNTS['update'] != 0:
self.stdout.write('{} LDAP users were were update with info '.format(self.COUNTS['update']))
if int(options['verbosity']) >= 2:
self.stdout.write('{} Inside users in sync with LDAP users '.format(self.COUNTS['in_sync']))
# Voila!
def get_inside_users_diffable(self):
inside_users_diffable = {}
group = InsideGroup.objects.get(posix_group='dns-aktiv')
group = InsideGroup.objects.get(posix_group=ACTIVE_USERS_GROUP)
inside_users = InsideUser.objects.filter(group_rels__group=group)
for u in inside_users:
@@ -73,17 +55,20 @@ class Command(BaseCommand):
inside_groups = inside_groups.values_list('posix_group', flat=True)
inside_users_diffable[u.ldap_username] = {
'username': u.ldap_username,
'first_name': u.firstname,
'last_name': u.lastname,
'password': u.ldap_password,
'first_name': u.first_name,
'last_name': u.last_name,
'ldap_password': u.ldap_password,
'email': u.email,
'groups': inside_groups
}
if self.options['verbosity'] == '3':
self.stdout.write('Found {} Inside users'.format(len(inside_users_diffable)))
return inside_users_diffable
def get_ldap_users_diffable(self):
ldap_users_diffable = {}
ldap_active_members = LdapGroup.objects.get(name="dns-aktiv").members
ldap_active_members = LdapGroup.objects.get(name=ACTIVE_USERS_GROUP).members
ldap_users = LdapUser.objects.filter(username__in=ldap_active_members)
for u in ldap_users:
@@ -95,8 +80,47 @@ class Command(BaseCommand):
'email': u.email,
'groups': ldap_groups
}
if self.options['verbosity'] == '3':
self.stdout.write('Found {} LDAP users'.format(len(ldap_users_diffable)))
return ldap_users_diffable
def sync_users(self, inside_users_diffable, ldap_users_diffable):
for username, u in inside_users_diffable.iteritems():
if username not in ldap_users_diffable:
# Create
add_new_user(u, dry_run=self.options['dry_run'])
self.COUNTS['create'] += 1
if int(self.options['verbosity']) >= 2:
self.stdout.write('Inside user {} is not in LDAP'.format(username))
elif not self.user_details_in_sync(u, ldap_users_diffable[username]):
# Update
ldap_update_user_details(u, dry_run=self.options['dry_run'])
self.COUNTS['update'] += 1
if int(self.options['verbosity']) >= 2:
self.stdout.write('Inside user {} is out of sync with LDAP'.format(username))
else:
# In sync :-)
self.COUNTS['in_sync'] += 1
if int(self.options['verbosity']) == 3:
self.stdout.write('Inside user {} is in sync with LDAP'.format(username))
def stale_ldap_users(self, inside_users_diffable, ldap_users_diffable):
for username, u in ldap_users_diffable.iteritems():
if username not in inside_users_diffable:
if int(self.options['verbosity']) >= 2:
self.stdout.write('LDAP user {} is not in Inside'.format(username))
def log_totals(self):
if self.COUNTS['create'] != 0:
self.stdout.write('{} Inside users not found in LDAP'.format(self.COUNTS['create']))
if self.COUNTS['update'] != 0:
self.stdout.write('{} LDAP users were were update with info '.format(self.COUNTS['update']))
if int(self.options['verbosity']) >= 2:
self.stdout.write('{} Inside users in sync with LDAP users '.format(self.COUNTS['in_sync']))
def user_details_in_sync(self, inside_user, ldap_user):
for attr in self.DIFF_ATTRIBUTES:
if inside_user[attr] != ldap_user[attr]:
+14 -4
View File
@@ -7,7 +7,7 @@ import os
logger = logging.getLogger(__name__)
def create_home_dir(username):
def create_home_dir(username, dry_run=False):
"""
Create homedir for username
- Copy /etc/skel
@@ -34,8 +34,18 @@ def create_home_dir(username):
logger.error("Path '{}:{}' already exists, doing nothing.".format(host, path))
return False
run('cp -r /etc/skel {}'.format(path))
run('chown -R {0}:{0} {1}'.format(username, path))
run('chmod -R 700 {}'.format(path))
cp_skel_cmd = 'cp -r /etc/skel {}'.format(path)
chown_cmd = 'chown -R {0}:{0} {1}'.format(username, path)
chmod_cmd = 'chmod -R 700 {}'.format(path)
if not dry_run:
run(cp_skel_cmd)
run(chown_cmd)
run(chmod_cmd)
else:
logger.debug('Creating homedir on {} with cmds: {} {} {}'.format(
host_string,
cp_skel_cmd,
chown_cmd,
chmod_cmd))
return True
+28 -1
View File
@@ -1,11 +1,38 @@
# coding: utf-8
from __future__ import unicode_literals
import base64
from neuf_ldap.utils import ldap_create_user
from neuf_radius.utils import radius_create_user
from neuf_userinfo.ssh import create_home_dir
import rijndael
import logging
logger = logging.getLogger(__name__)
def add_new_user(user, dry_run=False):
"""
- Create new user in LDAP (username, first_name, last_name, email)
- Add to groups (usergroup, dns-alle), it not exists, create
- Set LDAP password
- Create homedir on wii
- Set RADIUS password
- (Create automount entry)
- (Create kerberos principal and set password)
"""
results = {
'user': user,
'ldap_user': ldap_create_user(user, dry_run=dry_run),
'homedir': create_home_dir(user['username'], dry_run=dry_run),
# 'ldap_automount': ldap_create_automount(user['username']), # FIXME disabled
# 'kerberos_principal': kerberos_create_principal(user['username'], user['password']), # FIXME disabled
}
if user.get('password'):
results['radius'] = radius_create_user(user['username'], user['password'])
logger.debug(results)
"""
Rijndael stuff, thank you SO!
Ref: http://stackoverflow.com/questions/8217269/decrypting-strings-in-python-that-were-encrypted-with-mcrypt-rijndael-256-in-php#answers-header
@@ -48,4 +75,4 @@ def decrypt_rijndael(key, encoded):
padded_text += r.decrypt(ciphertext[start:start+BLOCK_SIZE])
plaintext = padded_text.split('\x00', 1)[0]
return plaintext
return plaintext
+10 -5
View File
@@ -1,9 +1,14 @@
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
import re
# Passwords should not contain "
password_re = re.compile(r'(?<!\")', re.IGNORECASE)
PasswordValidator = RegexValidator(password_re, u'Enter a valid password')
username_re = re.compile(r'^[a-z][a-z0-9]*$') # TODO Length
UsernameValidator = RegexValidator(username_re, u'Enter a valid username')
def validate_password(value):
# Passwords should not contain " and '
blacklist = ['"', '\'']
for c in blacklist:
if c in value:
raise ValidationError(u'Enter a valid password.', code='invalid')
username_re = re.compile(r'^[a-z][a-z0-9]{2,31}$') # From 3-32 alphanum chars (man useradd), starting with a char
UsernameValidator = RegexValidator(username_re, u'Enter a valid username.')
+3 -22
View File
@@ -17,11 +17,8 @@ from django.views.generic import View
import logging
from inside.models import InsideUser
from neuf_kerberos.utils import kerberos_create_principal
from neuf_ldap.utils import ldap_create_user, ldap_create_automount
from neuf_radius.utils import radius_create_user
from neuf_userinfo.forms import NewUserForm
from neuf_userinfo.ssh import create_home_dir
from neuf_userinfo.utils import add_new_user
logger = logging.getLogger(__name__)
@@ -117,15 +114,7 @@ def password_reset_confirm(request, uidb64=None, token=None,
class AddNewUserView(View):
def get(self, request):
"""
- Create new user in LDAP (username, first_name, last_name, email)
- Add to groups (usergroup, dns-alle), it not exists, create
- Set LDAP password
- Create automount entry
- Create kerberos principal and set password
- Create homedir on wii
- Set RADIUS password
"""
""" Adds a new user to all our internal services. See .utils.add_new_user for details. """
if not self.validate_api_key(request.GET.get('api_key', '')):
return JsonResponse({'errors': 'Invalid api_key'})
@@ -136,15 +125,7 @@ class AddNewUserView(View):
user = form.cleaned_data
results = {
'user': user,
'ldap_user': ldap_create_user(user),
'homedir': create_home_dir(user['username']),
'ldap_automount': ldap_create_automount(user['username']),
'kerberos_principal': kerberos_create_principal(user['username'], user['password']),
'radius': radius_create_user(user['username'], user['password'])
}
logger.debug(results)
add_new_user(user)
return JsonResponse({'results': 'success'})