Try ldap auth using python-ldap

This commit is contained in:
Nico Schottelius 2019-01-26 17:46:06 +01:00
parent 8cd904dcdc
commit 93677e6ad6
4 changed files with 69 additions and 78 deletions

View file

@ -4,6 +4,7 @@ LDAPSERVER="ldap://ldap1.ungleich.ch ldap://ldap2.ungleich.ch"
LDAPSEARCHUSER="user here" LDAPSEARCHUSER="user here"
LDAPSEARCHUSERPASSWORD="password here" LDAPSEARCHUSERPASSWORD="password here"
# Space separated list of search bases for users # Space separated list of search bases for users
LDAPSEARCH="ou=users,dc=ungleich,dc=ch ou=customers,dc=ungleich,dc=ch" LDAPSEARCH="ou=users,dc=ungleich,dc=ch ou=customers,dc=ungleich,dc=ch"
LDAPCREATE="ou=customers,dc=ungleich,dc=ch" LDAPCREATE="ou=customers,dc=ungleich,dc=ch"

View file

@ -28,6 +28,8 @@ search_base = os.environ['LDAPSEARCH'].split()
search_base_ldap = [ LDAPSearch(x, ldap.SCOPE_SUBTREE, "(uid=%(user)s)") for x in search_base ] search_base_ldap = [ LDAPSearch(x, ldap.SCOPE_SUBTREE, "(uid=%(user)s)") for x in search_base ]
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*search_base_ldap) AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(*search_base_ldap)
if os.environ['LDAP_USE_TLS']:
AUTH_LDAP_START_TLS=True
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

View file

@ -18,83 +18,75 @@ from datetime import datetime
from random import choice, randint from random import choice, randint
import string import string
import os
# Use ldap, like django_auth_backend
import ldap
from django.conf import settings from django.conf import settings
from django_auth_ldap.backend import LDAPBackend
from ldap3 import Server, ServerPool, Connection, ObjectDef, AttrDef, Reader, Writer
def check_user_exists(username):
user = LDAPBackend().populate_user(username)
if not user == None:
return True
else:
return False
class LDAP(object): class LDAP(object):
def __init__(self): def __init__(self):
self.server = settings.AUTH_LDAP_SERVER_URI self.uri = settings.AUTH_LDAP_SERVER_URI
self.user = settings.AUTH_LDAP_BIND_DN self.user = settings.AUTH_LDAP_BIND_DN
self.password = settings.AUTH_LDAP_BIND_PASSWORD self.password = settings.AUTH_LDAP_BIND_PASSWORD
# FIXME: take from settings # FIXME: take from settings
self.search = os.environ['LDAPSEARCH'] self.search_base = os.environ['LDAPSEARCH']
self.search_scope = ldap.SCOPE_SUBTREE
self.search_filter = "objectClass=inetOrgPerson"
# FIXME: hard coded # FIXME: hard coded
self.dn = "uid={{}},{}".format(os.environ['LDAPCREATE']) self.dn = "uid={{}},{}".format(os.environ['LDAPCREATE'])
self.gid = "10004"
self.conn = ldap.initialize(self.uri)
if settings.AUTH_LDAP_START_TLS:
self.conn.start_tls_s()
print("{} {} {}".format(self.uri, self.user, self.password))
self.conn.bind_s(self.user, self.password)
def check_user_exists(self, username):
result = self.conn.search_s(self.search_base,
self.search_scope,
self.dn.format(username))
if not len(result) == 0:
return True
else:
return False
def create_user(self, user, password, firstname, lastname, email): def create_user(self, user, password, firstname, lastname, email):
conn = Connection(self.server,
self.user,
self.password)
if not conn.bind():
raise Exception("Could not connect to LDAPserver {}".format(self.server))
# set objectClasses for the new user
obj_new_user = ObjectDef(['inetOrgPerson', 'posixAccount', 'ldapPublicKey'], conn)
w = Writer(conn, obj_new_user)
dn = self.dn.format(user) dn = self.dn.format(user)
w.new(dn) modlist = {
"objectClass": ["inetOrgPerson", "posixAccount", "ldapPublickey"],
"uid": [user],
"sn": [lastname],
"givenName": [firstname],
"cn": ["{} {}".format(firstname, lastname)],
"displayName": ["{} {}".format(firstname, lastname)],
"uidNumber": ["{}".format(self.get_new_uid_number(conn))],
"gidNumber": [self.gid],
"loginShell": ["/bin/bash"],
"homeDirectory": ["/home/{}".format(user)],
"mail": email,
"userPassword": password
}
result = self.conn.add_s(dn, ldap.modlist.addModlist(modlist))
# Filling in some of the data def get_new_uid_number(self):
# required attributes are sn, cn, homeDirectory, uid (already handled by dn), uidNumber, gidNumber uidlist = [0]
w[0].givenName = firstname for result in self.conn.search_s(self.search_base,
w[0].sn = lastname self.search_scope,
w[0].cn = firstname + " " + lastname self.search_filter):
w[0].mail = email
w[0].userPassword = password
w[0].homeDirectory = "/home/{}".format(user)
# Set uidNumber as last used uidNumber+1 uidlist.append(int(result[1]['uidNumber'][0]))
w[0].uidNumber = self.get_new_uid_number(conn)
# gidNumber for users created by userservice, nice and clear
w[0].gidNumber = 10004
if not w.commit(): return sorted(uidlist)[-1] + 1
conn.unbind()
raise Exception("Could not write new user {} to LDAP DB, commit error".format(user))
conn.unbind()
# Function to get the next uid number. Not elegant, but LAM does it too and didn't really find anything
# nicer. The sorted() seems to be quite efficient, so it shouldn't take too long even on larger arrays
def get_new_uid_number(self, conn):
newuid = 0
uidlist = []
for search in self.search.split():
conn.search(search, '(&(objectClass=posixAccount)(uidNumber=*))', attributes = [ 'uidNumber' ])
for c in conn.response:
uidlist.append(c['attributes']['uidNumber'])
# New uid is highest old uidnumber plus one
newuid = (sorted(uidlist)[len(uidlist)-1] + 1)
return newuid
class Index(View): class Index(View):
def get(self, request): def get(self, request):
@ -118,36 +110,40 @@ class Register(View):
# Someone filled out the register page, do some basic checks and throw it at nameko # Someone filled out the register page, do some basic checks and throw it at nameko
def post(self, request): def post(self, request):
# message for the error template l = LDAP()
service = 'register an user' service = 'register an user'
# urlname for 'go back' on the errorpage
urlname = 'register' urlname = 'register'
username = request.POST.get('username') username = request.POST.get('username')
if username == "" or not username: if username == "" or not username:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please supply a username.' } ) return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please supply a username.' } )
if check_user_exists(username):
if l.check_user_exists(username):
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'User already exists.' } ) return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'User already exists.' } )
password1 = request.POST.get('password1') password1 = request.POST.get('password1')
password2 = request.POST.get('password2') password2 = request.POST.get('password2')
# check if the supplied passwords match
if password1 != password2: if password1 != password2:
return render(request, 'error.html', { 'urlname': urlname, return render(request, 'error.html', { 'urlname': urlname,
'service': service, 'service': service,
'error': 'Your passwords did not match. Please supply the same password twice.' } ) 'error': "Passwords don't match." } )
email = request.POST.get('email') email = request.POST.get('email')
# Is the emailaddress valid?
try: try:
validate_email(email) validate_email(email)
except ValidationError: except ValidationError:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'The supplied email address is invalid.' } ) return render(request, 'error.html', { 'urlname': urlname,
'service': service,
'error': 'The supplied email address is invalid.' } )
firstname = request.POST.get('firstname') firstname = request.POST.get('firstname')
lastname = request.POST.get('lastname') lastname = request.POST.get('lastname')
if firstname == "" or not firstname or lastname == "" or not lastname: if not firstname or not lastname:
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter your firstname and lastname.' } ) return render(request, 'error.html', { 'urlname': urlname,
'service': service,
'error': 'Please enter your firstname and lastname.' } )
# so nothing strange happens if there are escapable chars # so nothing strange happens if there are escapable chars
pwd = r'%s' % password1 pwd = r'%s' % password1
@ -163,7 +159,6 @@ class Register(View):
return render(request, 'usercreated.html', { 'user': username } ) return render(request, 'usercreated.html', { 'user': username } )
class ChangeData(View): class ChangeData(View):
# provide the form for the change request # provide the form for the change request
def get(self, request): def get(self, request):
@ -220,17 +215,10 @@ class ChangeData(View):
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } ) return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
# Resets the password for a user
# Sends email to the user with a link to reset the password
class ResetPassword(View): class ResetPassword(View):
# Presents the form with some information
def get(self, request): def get(self, request):
return render(request, 'resetpassword.html') return render(request, 'resetpassword.html')
# gets the data from confirming the reset request and checks if it was not a misclick
# (by having the user type in his username)
def post(self, request): def post(self, request):
urlname = 'reset_password' urlname = 'reset_password'
service = 'send a password reset request' service = 'send a password reset request'

View file

@ -1,4 +1,4 @@
django>=2.1.2 django
django-auth-ldap>=1.7.0 django-auth-ldap
ldap3>=2.5.1 python-ldap
django-dotenv django-dotenv