New user: Initial LDAP work

This commit is contained in:
nikolark committed 2015-02-20 23:34:04 +01:00
1 parent 425f4cc3cb
commit 166a02e7cb
6 files changed
+165 -97

No files matched your search

+28 -20
View File
@@ -1,4 +1,5 @@
import datetime
from django.conf import settings
import ldapdb.models
from ldapdb.models.fields import CharField, ImageField, IntegerField, ListField
@@ -7,15 +8,14 @@ from neuf_ldap.utils import ldap_create, ldap_validate
class LdapUser(ldapdb.models.Model):
"""
Represents an LDAP posixAccount,inetOrgPerson entry.
Ref:
- http://www.zytrax.com/books/ldap/apa/types.html
Represents an LDAP posixAccount, inetOrgPerson, shadowAccount entry.
Ref: http://www.zytrax.com/books/ldap/apa/types.html
"""
connection_name = 'ldap'
# LDAP meta-data
base_dn = "ou=People,dc=neuf,dc=no" # FXIME: CONFIG
base_dn = settings.LDAP_USER_DN
object_classes = ['inetOrgPerson', 'posixAccount', 'shadowAccount']
# inetOrgPerson
@@ -31,33 +31,41 @@ class LdapUser(ldapdb.models.Model):
id = IntegerField(db_column='uidNumber', unique=True) # referenced in reset password form
group = IntegerField(db_column='gidNumber')
gecos = CharField(db_column='gecos')
display_name = CharField(db_column='displayname')
home_directory = CharField(db_column='homeDirectory')
login_shell = CharField(db_column='loginShell', default='/bin/bash')
login_shell = CharField(db_column='loginShell', default=settings.LDAP_LOGIN_SHELL)
username = CharField(db_column='uid', primary_key=True)
# shadowAccount
password = CharField(db_column='userPassword')
shadowLastChange = CharField(db_column='shadowLastChange', default='10877') # Magic number?
shadowMin = CharField(db_column='shadowMin', default='8')
shadowMax = CharField(db_column='shadowMax', default='999999')
shadowWarning = CharField(db_column='shadowWarning', default='7')
shadowInactive = CharField(db_column='shadowInactive') # FIXME what should this be?
shadowExpire = CharField(db_column='shadowExpire', default='-1')
shadowFlag = CharField(db_column='shadowFlag', default='0')
shadowLastChange = IntegerField(db_column='shadowLastChange', default=settings.LDAP_SHADOW_LAST_CHANGE)
shadowMin = IntegerField(db_column='shadowMin', default=settings.LDAP_SHADOW_MIN)
shadowMax = IntegerField(db_column='shadowMax', default=settings.LDAP_SHADOW_MAX)
shadowWarning = IntegerField(db_column='shadowWarning', default=settings.LDAP_SHADOW_WARNING)
shadowInactive = IntegerField(db_column='shadowInactive')
shadowExpire = IntegerField(db_column='shadowExpire', default=settings.LDAP_SHADOW_EXPIRE)
shadowFlag = IntegerField(db_column='shadowFlag', default=settings.LDAP_SHADOW_FLAG)
# core
description = CharField(db_column='description')
# ugly hack to use django internals for password reset
# Hack to use django internals for password reset
last_login = datetime.datetime(2001, 1, 1)
def set_password(self, raw_password):
if raw_password is None:
self.password = "!"
return
def _days_since_1970(self):
epoch = datetime.datetime.utcfromtimestamp(0)
today = datetime.datetime.today()
delta = today - epoch
return delta.days
def set_password(self, raw_password, commit=True):
self.password = ldap_create(raw_password)
self.save()
# Update last changed password date
self.shadowLastChange = self._days_since_1970()
if commit:
self.save()
def check_password(self, raw_password):
return ldap_validate(raw_password, self.password)
@@ -72,13 +80,13 @@ class LdapGroup(ldapdb.models.Model):
connection_name = 'ldap'
# LDAP meta-data
base_dn = "ou=Groups,dc=neuf,dc=no" # CONFIG
base_dn = settings.LDAP_GROUP_DN
object_classes = ['posixGroup']
# posixGroup attributes
gid = IntegerField(db_column='gidNumber', unique=True)
name = CharField(db_column='cn', max_length=200, primary_key=True)
usernames = ListField(db_column='memberUid')
members = ListField(db_column='memberUid')
def __unicode__(self):
return self.name
+80 -43
View File
@@ -1,51 +1,23 @@
import hashlib
# coding: utf-8
from __future__ import unicode_literals
import json
from django.conf import settings
import os
from base64 import encodestring as encode
from base64 import decodestring as decode
import os.path
from passlib.hash import ldap_salted_sha1
import logging
logger = logging.getLogger(__name__)
"""
Ref:
- http://www.openldap.org/faq/data/cache/347.html
- http://www.openldap.org/doc/admin24/security.html
"""
# TODO: Move this to settings file?
BASE_DN = "dc=neuf,dc=no"
KERBEROS_DN = "cn=krbcontainer,{}".format(BASE_DN)
AUTOMOUNT_DN = "ou=Automount,{}".format(BASE_DN)
USER_DN = "ou=People,{}".format(BASE_DN)
GROUP_DN = "ou=Groups,{}".format(BASE_DN)
FILESERVER_HOST = "wii.neuf.no"
FILESERVER_HOME_PATH = "/fileserver/homes"
UID_MIN = "10000"
UID_MAX = "100000"
GID_MIN = "9000"
GID_MAX = "9999"
USER_GID_MIN = "10000"
USER_GID_MAX = "100000"
def ldap_create(raw_password, hash_type='ssha'):
# Generate a salted SHA1 hash
salt = os.urandom(4)
h = hashlib.sha1(raw_password)
h.update(salt)
return "{SSHA}" + encode(h.digest() + salt)[:-1]
def ldap_create(raw_password):
return ldap_salted_sha1.encrypt(raw_password)
def ldap_validate(raw_password, challenge_password):
# challenge_password is hash from db
# raw_password is the cleartext password.
challenge_bytes = decode(challenge_password[6:])
digest = challenge_bytes[:20]
salt = challenge_bytes[20:]
hr = hashlib.sha1(raw_password)
hr.update(salt)
return digest == hr.digest()
return ldap_salted_sha1.validate(raw_password, challenge_password)
def set_ldap_password(username, raw_password):
@@ -58,10 +30,75 @@ def set_ldap_password(username, raw_password):
pass
def ldap_create_user(user):
def ldap_user_group_exists(username):
from neuf_ldap.models import LdapGroup
return len(LdapGroup.objects.filter(name=username)) != 0
def ldap_username_exists(username):
from neuf_ldap.models import LdapUser
u = LdapUser()
# TODO
return len(LdapUser.objects.filter(username=username)) != 0
def ldap_create_user(user):
from neuf_ldap.models import LdapUser, LdapGroup
def _get_next_uid():
# Get user ids between min and max, and order desc by id
users = LdapUser.objects.filter(id__gte=settings.LDAP_UID_MIN, id__lte=settings.LDAP_UID_MAX)
users = users.order_by('-id').values_list('id', flat=True)
if len(users) == 0:
return settings.LDAP_UID_MIN
if len(users) > 0 and users[0] >= settings.LDAP_UID_MAX:
logger.exception("UID larger than LDAP_UID_MAX")
return users[0] + 1
def _get_next_user_gid():
# Get user group ids between min and max, and order desc by id
user_groups = LdapGroup.objects.filter(gid__gte=settings.LDAP_USER_GID_MIN, gid__lte=settings.LDAP_USER_GID_MAX)
user_groups = user_groups.order_by('-gid').values_list('gid', flat=True)
if len(user_groups) > 0 and user_groups[0] >= settings.LDAP_USER_GID_MAX:
logger.exception("UID larger than LDAP_USER_GID_MAX")
return user_groups[0] + 1
groups = user.pop('groups')
# User
full_name = '{} {}'.format(user['firstname'], user['lastname'])
user_data = {
'first_name': user['firstname'],
'last_name': user['lastname'],
'full_name': full_name,
'display_name': full_name,
'gecos': full_name,
'email': user['email'],
'username': user['username'],
'id': _get_next_uid(),
'group': _get_next_user_gid(),
'home_directory': os.path.join(settings.LDAP_HOME_DIRECTORY_PREFIX, user['username'])
}
ldap_user = LdapUser(**user_data)
ldap_user.set_password(user['password'], commit=False)
ldap_user.save()
# Add user group
ldap_user_group = LdapGroup(name=user['username'], gid=user_data['group'], members=[user['username']])
ldap_user_group.save()
# Add groups
ldap_groups = LdapGroup.objects.filter(name__in=groups)
for g in ldap_groups:
if user['username'] not in g.members:
g.members.append(user['username'])
g.save()
# Finito!
def ldap_create_automount(user):
+2 -4
View File
@@ -1,12 +1,10 @@
import hashlib
from passlib.hash import nthash
from neuf_radius.models import Radcheck
def radius_create(raw_password):
# Found in the wild.
nt_password = hashlib.new('md4', raw_password.encode('utf-16le')).hexdigest()
return nt_password
return nthash.encrypt(raw_password)
def set_radius_password(username, raw_password):
+8 -1
View File
@@ -16,8 +16,9 @@ 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_radius.utils import set_radius_password
from neuf_ldap.utils import set_ldap_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
@@ -132,6 +133,12 @@ class NewUserForm(forms.Form):
username = self.cleaned_data['username']
UsernameValidator(username)
if ldap_username_exists(username):
raise ValidationError("User '{}' already exists in LDAP".format(username))
if ldap_user_group_exists(username):
raise ValidationError("User '{}' already has a user group in LDAP".format(username))
return username
def clean_password(self):
+10 -9
View File
@@ -150,16 +150,17 @@ class AddNewUserView(View):
user = form.cleaned_data
ldap_create_user(user)
ldap_create_automount(user)
kerberos_create_principal(user)
create_homedir(user.username) # FIXME run ssh script?
radius_create_user(user.username, user.password)
# print "yey", user
# TODO you are still here
results = {
'user': user,
'ldap_user': ldap_create_user(user),
'ldap_automount': ldap_create_automount(user),
'kerberos_principal': kerberos_create_principal(user),
'homedir': create_homedir(user['username']), # FIXME run ssh script?
'radius': radius_create_user(user['username'], user['password'])
}
# import pprint; pprint.pprint(results)
result = {'yousir': 'are a scholar!'}
return JsonResponse(result)
return JsonResponse({'results': 'success'})
def validate_api_key(self, api_key):
if api_key == '' or api_key != settings.INSIDE_USERSYNC_API_KEY:
+37 -20
View File
@@ -62,6 +62,29 @@ TEMPLATE_DIRS = (
WSGI_APPLICATION = 'userinfosite.wsgi.application'
# LDAP (neuf.no)
LDAP_BASE_DN = "dc=neuf,dc=no"
LDAP_USER_DN = "ou=People,{}".format(LDAP_BASE_DN)
LDAP_GROUP_DN = "ou=Groups,{}".format(LDAP_BASE_DN)
LDAP_KERBEROS_DN = "cn=krbcontainer,{}".format(LDAP_BASE_DN)
LDAP_AUTOMOUNT_DN = "ou=Automount,{}".format(LDAP_BASE_DN)
LDAP_UID_MIN = 10000
LDAP_UID_MAX = 100000
LDAP_GID_MIN = 9000
LDAP_GID_MAX = 9999
LDAP_USER_GID_MIN = 10000
LDAP_USER_GID_MAX = 100000
LDAP_LOGIN_SHELL = '/bin/bash'
LDAP_HOME_DIRECTORY_PREFIX = '/home'
# Ref: http://tille.garrels.be/training/ldap/ch02s02.html
LDAP_SHADOW_LAST_CHANGE = 0 # Days since password last change
LDAP_SHADOW_MIN = 8 #
LDAP_SHADOW_MAX = 999999
LDAP_SHADOW_WARNING = 7 #
LDAP_SHADOW_EXPIRE = -1 #
LDAP_SHADOW_FLAG = 0
# Database
# https://docs.djangoproject.com/en/1.6/ref/settings/#databases
@@ -73,14 +96,14 @@ DATABASES = {
'ldap': {
'ENGINE': 'ldapdb.backends.ldap',
'NAME': 'ldap://localhost/',
'USER': 'uid=test,ou=People,dc=neuf,dc=no',
'USER': 'uid=test,{}'.format(LDAP_USER_DN),
'PASSWORD': 'test',
},
'radius': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'radius',
'USER': 'radius',
'PASSWORD': '',
'NAME': 'dev_radius',
'USER': 'dev',
'PASSWORD': 'dev',
'HOST': 'localhost',
},
'inside': {
@@ -138,21 +161,17 @@ import ldap
from django_auth_ldap.config import LDAPSearch, PosixGroupType
# Basic user auth
AUTH_LDAP_U_USER_SEARCH = LDAPSearch("ou=People,dc=neuf,dc=no", ldap.SCOPE_ONELEVEL, "(uid=%(user)s)")
AUTH_LDAP_U_USER_SEARCH = LDAPSearch(LDAP_USER_DN, ldap.SCOPE_ONELEVEL, "(uid=%(user)s)")
# Basic groups
AUTH_LDAP_U_GROUP_SEARCH = LDAPSearch("ou=Groups,dc=neuf,dc=no", ldap.SCOPE_ONELEVEL, "(objectClass=posixGroup)")
AUTH_LDAP_U_GROUP_SEARCH = LDAPSearch(LDAP_GROUP_DN, ldap.SCOPE_ONELEVEL, "(objectClass=posixGroup)")
AUTH_LDAP_U_GROUP_TYPE = PosixGroupType()
# Mirror groups on each auth
AUTH_LDAP_U_MIRROR_GROUPS = True
# Group to user flag mappings
AUTH_LDAP_U_USER_FLAGS_BY_GROUP = {
"is_active": "cn=dns-alle,ou=Groups,dc=neuf,dc=no",
"is_staff": "cn=edb,ou=Groups,dc=neuf,dc=no",
"is_superuser": "cn=edbadmin,ou=Groups,dc=neuf,dc=no"
}
# Group to profile flag mappings, not used.
AUTH_LDAP_U_PROFILE_FLAGS_BY_GROUP = {
# "is_edb": "cn=edb,ou=Groups,dc=neuf,dc=no"
"is_active": "cn=dns-alle,{}".format(LDAP_GROUP_DN),
"is_staff": "cn=edb,{}".format(LDAP_GROUP_DN),
"is_superuser": "cn=edbadmin,{}".format(LDAP_GROUP_DN)
}
# User attribute mappings
AUTH_LDAP_U_USER_ATTR_MAP = {
@@ -160,10 +179,6 @@ AUTH_LDAP_U_USER_ATTR_MAP = {
"last_name": "sn",
"email": "mail",
}
# User profile attribute mappings
AUTH_LDAP_U_PROFILE_ATTR_MAP = {
"home_directory": "homeDirectory"
}
# Allways update the django user object on authentication.
AUTH_LDAP_U_ALWAYS_UPDATE_USER = True
@@ -172,15 +187,13 @@ AUTH_LDAP_E_SERVER_URI = AUTH_LDAP_U_SERVER_URI
AUTH_LDAP_E_BIND_DN = AUTH_LDAP_U_BIND_DN
AUTH_LDAP_E_BIND_PASSWORD = AUTH_LDAP_U_BIND_PASSWORD
AUTH_LDAP_E_USER_SEARCH = LDAPSearch("ou=People,dc=neuf,dc=no", ldap.SCOPE_ONELEVEL, "(mail=%(user)s)")
AUTH_LDAP_E_USER_SEARCH = LDAPSearch(LDAP_USER_DN, ldap.SCOPE_ONELEVEL, "(mail=%(user)s)")
AUTH_LDAP_E_GROUP_SEARCH = AUTH_LDAP_U_GROUP_SEARCH
AUTH_LDAP_E_GROUP_TYPE = AUTH_LDAP_U_GROUP_TYPE
AUTH_LDAP_E_MIRROR_GROUPS = AUTH_LDAP_U_MIRROR_GROUPS
AUTH_LDAP_E_USER_FLAGS_BY_GROUP = AUTH_LDAP_U_USER_FLAGS_BY_GROUP
AUTH_LDAP_E_PROFILE_FLAGS_BY_GROUP = AUTH_LDAP_U_PROFILE_FLAGS_BY_GROUP
AUTH_LDAP_E_USER_ATTR_MAP = AUTH_LDAP_U_USER_ATTR_MAP
AUTH_LDAP_E_PROFILE_ATTR_MAP = AUTH_LDAP_U_PROFILE_ATTR_MAP
AUTH_LDAP_E_ALWAYS_UPDATE_USER = AUTH_LDAP_U_ALWAYS_UPDATE_USER
# Debug logging
@@ -199,6 +212,10 @@ INSIDE_GROUPS_SYNC_DELETE = True # When user logs in, groups are synced (locall
INSIDE_USERSYNC_API_KEY = ''
INSIDE_USERSYNC_ENC_KEY = ''
# Home dir
FILESERVER_HOST = "wii.neuf.no"
FILESERVER_HOME_PATH = "/fileserver/homes"
try:
from local_settings import *
except ImportError: