From 93677e6ad67222ba7a772aff5c8fdf7d0bf9ad44 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 26 Jan 2019 17:46:06 +0100 Subject: [PATCH] Try ldap auth using python-ldap --- dal/dal/env.sample | 1 + dal/dal/settings.py | 2 + dal/dal/views.py | 138 ++++++++++++++++++++------------------------ requirements.txt | 6 +- 4 files changed, 69 insertions(+), 78 deletions(-) diff --git a/dal/dal/env.sample b/dal/dal/env.sample index 0e81220..a27b883 100644 --- a/dal/dal/env.sample +++ b/dal/dal/env.sample @@ -4,6 +4,7 @@ LDAPSERVER="ldap://ldap1.ungleich.ch ldap://ldap2.ungleich.ch" LDAPSEARCHUSER="user here" LDAPSEARCHUSERPASSWORD="password here" + # Space separated list of search bases for users LDAPSEARCH="ou=users,dc=ungleich,dc=ch ou=customers,dc=ungleich,dc=ch" LDAPCREATE="ou=customers,dc=ungleich,dc=ch" diff --git a/dal/dal/settings.py b/dal/dal/settings.py index 05abe0f..a284663 100644 --- a/dal/dal/settings.py +++ b/dal/dal/settings.py @@ -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 ] 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__))) diff --git a/dal/dal/views.py b/dal/dal/views.py index fa2c684..250cd1a 100644 --- a/dal/dal/views.py +++ b/dal/dal/views.py @@ -18,83 +18,75 @@ from datetime import datetime from random import choice, randint import string +import os + +# Use ldap, like django_auth_backend +import ldap + 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): 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.password = settings.AUTH_LDAP_BIND_PASSWORD # 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 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): - 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) - 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 - # required attributes are sn, cn, homeDirectory, uid (already handled by dn), uidNumber, gidNumber + def get_new_uid_number(self): + uidlist = [0] - w[0].givenName = firstname - w[0].sn = lastname - w[0].cn = firstname + " " + lastname - w[0].mail = email - w[0].userPassword = password - w[0].homeDirectory = "/home/{}".format(user) + for result in self.conn.search_s(self.search_base, + self.search_scope, + self.search_filter): - # Set uidNumber as last used uidNumber+1 - w[0].uidNumber = self.get_new_uid_number(conn) - # gidNumber for users created by userservice, nice and clear - w[0].gidNumber = 10004 + uidlist.append(int(result[1]['uidNumber'][0])) - if not w.commit(): - 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 + return sorted(uidlist)[-1] + 1 class Index(View): 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 def post(self, request): - # message for the error template + l = LDAP() + service = 'register an user' - # urlname for 'go back' on the errorpage urlname = 'register' username = request.POST.get('username') + if username == "" or not 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.' } ) + password1 = request.POST.get('password1') password2 = request.POST.get('password2') - - # check if the supplied passwords match if password1 != password2: return render(request, 'error.html', { 'urlname': urlname, 'service': service, - 'error': 'Your passwords did not match. Please supply the same password twice.' } ) + 'error': "Passwords don't match." } ) email = request.POST.get('email') - # Is the emailaddress valid? try: validate_email(email) 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') lastname = request.POST.get('lastname') - if firstname == "" or not firstname or lastname == "" or not lastname: - return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter your firstname and lastname.' } ) + if not firstname or not 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 pwd = r'%s' % password1 @@ -163,7 +159,6 @@ class Register(View): return render(request, 'usercreated.html', { 'user': username } ) - class ChangeData(View): # provide the form for the change request def get(self, request): @@ -220,17 +215,10 @@ class ChangeData(View): 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): - - # Presents the form with some information def get(self, request): 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): urlname = 'reset_password' service = 'send a password reset request' diff --git a/requirements.txt b/requirements.txt index 0f502d8..0767c8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -django>=2.1.2 -django-auth-ldap>=1.7.0 -ldap3>=2.5.1 +django +django-auth-ldap +python-ldap django-dotenv