Add ldap user create support
This commit is contained in:
parent
a7e6bdeb42
commit
8cd904dcdc
3 changed files with 461 additions and 64 deletions
|
@ -6,3 +6,4 @@ 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"
|
||||||
|
|
174
dal/dal/views.py
174
dal/dal/views.py
|
@ -17,52 +17,91 @@ from datetime import datetime
|
||||||
|
|
||||||
from random import choice, randint
|
from random import choice, randint
|
||||||
import string
|
import string
|
||||||
from configparser import ConfigParser
|
|
||||||
|
|
||||||
config = ConfigParser()
|
from django.conf import settings
|
||||||
config.read('userservice.conf')
|
from django_auth_ldap.backend import LDAPBackend
|
||||||
|
from ldap3 import Server, ServerPool, Connection, ObjectDef, AttrDef, Reader, Writer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Check to see if the username is already taken
|
|
||||||
# Helper function, not to be set up as a view
|
|
||||||
# Check the LDAP if the user exists
|
|
||||||
def check_user_exists(username):
|
def check_user_exists(username):
|
||||||
with get_pool().next() as rpc:
|
user = LDAPBackend().populate_user(username)
|
||||||
return rpc.userlookup.lookup(username)
|
|
||||||
|
|
||||||
# To trick the tokengenerator to work with us, because we don't really
|
if not user == None:
|
||||||
# have the expected user Class since we are reading the user from a form
|
return True
|
||||||
# We store the tokens and don't have to use the check function,
|
else:
|
||||||
# some one time data works fine.
|
return False
|
||||||
|
|
||||||
class LastLogin():
|
|
||||||
|
|
||||||
def replace(self, microsecond=0, tzinfo=None):
|
|
||||||
return randint(1,100000)
|
|
||||||
|
|
||||||
class PseudoUser():
|
|
||||||
# easiest way to handle the check for lastlogin
|
|
||||||
last_login = LastLogin()
|
|
||||||
# random alphanumeric strings for primary key and password, just used for token generation
|
|
||||||
pk = ''.join(choice(string.ascii_letters + string.digits) for _ in range(20))
|
|
||||||
password = ''.join(choice(string.ascii_letters + string.digits) for _ in range(30))
|
|
||||||
|
|
||||||
|
|
||||||
# The index page
|
class LDAP(object):
|
||||||
# If there's a session open, it will give the user the options he/she/it can do, if not,
|
def __init__(self):
|
||||||
# it will show a landing page explaining what this is and prompt them to login
|
self.server = 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']
|
||||||
|
|
||||||
|
# FIXME: hard coded
|
||||||
|
self.dn = "uid={{}},{}".format(os.environ['LDAPCREATE'])
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Filling in some of the data
|
||||||
|
# required attributes are sn, cn, homeDirectory, uid (already handled by dn), uidNumber, gidNumber
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
class Index(View):
|
class Index(View):
|
||||||
|
|
||||||
# Basic binary choice, if it is an authenticated user, go straight to the options page,
|
|
||||||
# if not, then show the landing page
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
return render(request, 'useroptions.html', { 'user': request.user } )
|
return render(request, 'useroptions.html', { 'user': request.user } )
|
||||||
return render(request, 'landing.html')
|
return render(request, 'landing.html')
|
||||||
|
|
||||||
# Basically does the same as the GET request, just with trying to login the user beforehand
|
|
||||||
# Shows an errorpage if authentication fails, since just looping to the landing page
|
|
||||||
# would be frustrating
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
username = request.POST.get('username')
|
username = request.POST.get('username')
|
||||||
password = request.POST.get('password')
|
password = request.POST.get('password')
|
||||||
|
@ -73,12 +112,7 @@ class Index(View):
|
||||||
return render(request, 'useroptions.html', { 'user': user } )
|
return render(request, 'useroptions.html', { 'user': user } )
|
||||||
return render(request, 'loginfailed.html')
|
return render(request, 'loginfailed.html')
|
||||||
|
|
||||||
|
|
||||||
# Registering a user
|
|
||||||
|
|
||||||
class Register(View):
|
class Register(View):
|
||||||
|
|
||||||
# Someone wants to register, throw up the page for that
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return render(request, 'registeruser.html')
|
return render(request, 'registeruser.html')
|
||||||
|
|
||||||
|
@ -91,23 +125,17 @@ class Register(View):
|
||||||
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.' } )
|
||||||
# Check to see if username is already taken
|
|
||||||
# isalnum() may be a bit harsh, but is the most logical choice to make sure it's a username we
|
if check_user_exists(username):
|
||||||
# can use
|
|
||||||
if not username.isalnum():
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Username has to be alphanumeric.' } )
|
|
||||||
elif 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
|
# check if the supplied passwords match
|
||||||
if password1 != password2:
|
if password1 != password2:
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
|
return render(request, 'error.html', { 'urlname': urlname,
|
||||||
'error': 'Your passwords did not match. Please supply the same password twice.' } )
|
'service': service,
|
||||||
# check for at least a bit of length on the password
|
'error': 'Your passwords did not match. Please supply the same password twice.' } )
|
||||||
if len(password1) < 8:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service,
|
|
||||||
'error': 'Your password is too short, please use a longer one. At least 8 characters.' } )
|
|
||||||
|
|
||||||
email = request.POST.get('email')
|
email = request.POST.get('email')
|
||||||
# Is the emailaddress valid?
|
# Is the emailaddress valid?
|
||||||
|
@ -120,23 +148,23 @@ class Register(View):
|
||||||
lastname = request.POST.get('lastname')
|
lastname = request.POST.get('lastname')
|
||||||
if firstname == "" or not firstname or lastname == "" or not 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.' } )
|
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': 'Please enter your firstname and lastname.' } )
|
||||||
# throw it to nameko to create the user
|
|
||||||
with get_pool().next() as rpc:
|
|
||||||
# so nothing strange happens if there are escapable chars
|
|
||||||
pwd = r'%s' % password1
|
|
||||||
result = rpc.createuser.create_user(username, pwd, firstname, lastname, email)
|
|
||||||
if result == True:
|
|
||||||
return render(request, 'usercreated.html', { 'user': username } )
|
|
||||||
else:
|
|
||||||
return render(request, 'error.html', { 'urlname': urlname, 'service': service, 'error': result } )
|
|
||||||
|
|
||||||
|
# so nothing strange happens if there are escapable chars
|
||||||
|
pwd = r'%s' % password1
|
||||||
|
|
||||||
|
l = LDAP()
|
||||||
|
|
||||||
|
try:
|
||||||
|
l.create_user(username, pwd, firstname, lastname, email)
|
||||||
|
except Exception as e:
|
||||||
|
return render(request, 'error.html', { 'urlname': urlname,
|
||||||
|
'service': service,
|
||||||
|
'error': e } )
|
||||||
|
|
||||||
|
return render(request, 'usercreated.html', { 'user': username } )
|
||||||
|
|
||||||
# Change user data for logged in users
|
|
||||||
|
|
||||||
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):
|
||||||
urlname = 'change_data'
|
urlname = 'change_data'
|
||||||
|
@ -395,7 +423,6 @@ class ChangePassword(View):
|
||||||
|
|
||||||
# Deletes an account
|
# Deletes an account
|
||||||
class DeleteAccount(View):
|
class DeleteAccount(View):
|
||||||
|
|
||||||
# Show the basic form for deleting an account
|
# Show the basic form for deleting an account
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
return render(request, 'deleteaccount.html')
|
return render(request, 'deleteaccount.html')
|
||||||
|
@ -431,7 +458,26 @@ class DeleteAccount(View):
|
||||||
|
|
||||||
# Log out the session
|
# Log out the session
|
||||||
class LogOut(View):
|
class LogOut(View):
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
logout(request)
|
logout(request)
|
||||||
return HttpResponse("You have been logged out.", status=200)
|
return HttpResponse("You have been logged out.", status=200)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TO be clarified
|
||||||
|
|
||||||
|
# To trick the tokengenerator to work with us, because we don't really
|
||||||
|
# have the expected user Class since we are reading the user from a form
|
||||||
|
# We store the tokens and don't have to use the check function,
|
||||||
|
# some one time data works fine.
|
||||||
|
|
||||||
|
class LastLogin():
|
||||||
|
def replace(self, microsecond=0, tzinfo=None):
|
||||||
|
return randint(1,100000)
|
||||||
|
|
||||||
|
class PseudoUser():
|
||||||
|
# easiest way to handle the check for lastlogin
|
||||||
|
last_login = LastLogin()
|
||||||
|
# random alphanumeric strings for primary key and password, just used for token generation
|
||||||
|
pk = ''.join(choice(string.ascii_letters + string.digits) for _ in range(20))
|
||||||
|
password = ''.join(choice(string.ascii_letters + string.digits) for _ in range(30))
|
||||||
|
|
350
nameko-func.py
Normal file
350
nameko-func.py
Normal file
|
@ -0,0 +1,350 @@
|
||||||
|
from nameko.events import EventDispatcher, event_handler
|
||||||
|
from nameko.rpc import rpc
|
||||||
|
from configparser import ConfigParser
|
||||||
|
from ldap3 import Server, ServerPool, Connection, ObjectDef, AttrDef, Reader, Writer
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# For testing
|
||||||
|
from random import randint
|
||||||
|
|
||||||
|
# Read the config in the nameko.conf
|
||||||
|
config = ConfigParser()
|
||||||
|
config.read('nameko.conf')
|
||||||
|
|
||||||
|
# Sanity check for config
|
||||||
|
try:
|
||||||
|
mult_server = int(config['LDAP']['SERVERMULTIPLE'])
|
||||||
|
# SERVERMULTIPLE is set to something not a number
|
||||||
|
except:
|
||||||
|
exit("[LDAP] SERVERMULTIPLE has to be an integer >= 1")
|
||||||
|
# less than one server is not a sensible option
|
||||||
|
if mult_server < 1:
|
||||||
|
exit("[LDAP] SERVERMULTIPLE has to be an integer >= 1")
|
||||||
|
|
||||||
|
|
||||||
|
# Function to setup the server or serverpool
|
||||||
|
def ldapservers():
|
||||||
|
# Just one server, no need for a pool
|
||||||
|
if mult_server == 1:
|
||||||
|
ldapserver = Server(config['LDAP']['LDAPSERVER1'], use_ssl=True)
|
||||||
|
return ldapserver
|
||||||
|
# Multiple servers, set up a pool
|
||||||
|
else:
|
||||||
|
ldapserver = ServerPool(None)
|
||||||
|
for x in range(1, (mult_server+1)):
|
||||||
|
ins = Server(config['LDAP']['LDAPSERVER' + str(x)], use_ssl=True)
|
||||||
|
ldapserver.add(ins)
|
||||||
|
return ldapserver
|
||||||
|
|
||||||
|
|
||||||
|
# Since there's no reason why someone in ou=users shouldn't use the service,
|
||||||
|
# here's the helper function to check whether an uid is in ou=customers or
|
||||||
|
# ou=users
|
||||||
|
# returns the full dn
|
||||||
|
def user_or_customer(uid):
|
||||||
|
server = ldapservers()
|
||||||
|
conn = Connection(server, config['LDAP']['LDAPMANAGER'], config.get('LDAP','LDAPMANAGERPASSWORD', raw=True))
|
||||||
|
conn.bind()
|
||||||
|
search_customers = conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % uid)
|
||||||
|
if search_customers:
|
||||||
|
conn.unbind()
|
||||||
|
return '%s,ou=customers,dc=ungleich,dc=ch' % uid
|
||||||
|
search_users = conn.search('ou=users,dc=ungleich,dc=ch', '(%s)' % uid)
|
||||||
|
if search_users:
|
||||||
|
conn.unbind()
|
||||||
|
return '%s,ou=users,dc=ungleich,dc=ch' % uid
|
||||||
|
conn.unbind()
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get the objectclasses
|
||||||
|
def objclasses(rdn, uid, connection):
|
||||||
|
# search for objectClasses
|
||||||
|
connection.search(rdn, '(%s)' % uid, attributes=['objectClass'])
|
||||||
|
objclass = []
|
||||||
|
# get the relevant data
|
||||||
|
tmp = connection.entries[0]['objectClass']
|
||||||
|
# This one sets up the array
|
||||||
|
for y in tmp:
|
||||||
|
objclass.append(y)
|
||||||
|
# return the array containing the objectClasses, like ['inetOrgPerson', 'posixAccount', 'ldapPublicKey']
|
||||||
|
return objclass
|
||||||
|
|
||||||
|
# checks if a user already exists in the LDAP
|
||||||
|
class UserLookUp(object):
|
||||||
|
name = "userlookup"
|
||||||
|
dispatch = EventDispatcher()
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
def lookup(self, user):
|
||||||
|
# Setup the search parameter and connect to LDAP
|
||||||
|
LDAP_UID = 'uid=%s' % user
|
||||||
|
server = ldapservers()
|
||||||
|
conn = Connection(server, config['LDAP']['LDAPMANAGER'], config.get('LDAP','LDAPMANAGERPASSWORD', raw=True))
|
||||||
|
conn.bind()
|
||||||
|
# Strange result. It keeps complaining LDAP_UID not set if I try to directly
|
||||||
|
# substitute x and y to the if
|
||||||
|
# Searches for user in ou=customers and ou=users
|
||||||
|
x = conn.search('ou=customers,dc=ungleich,dc=ch', '(%s)' % LDAP_UID)
|
||||||
|
y = conn.search('ou=users,dc=ungleich,dc=ch', '(%s)' % LDAP_UID)
|
||||||
|
if x or y:
|
||||||
|
# return conn.entries[0] for first search result since we can assume uid is unique
|
||||||
|
self.dispatch('ldap', '%s [Info: UserLookUp] Searched for %s and found it\n' % (datetime.now(), LDAP_UID) )
|
||||||
|
conn.unbind()
|
||||||
|
# return True since the user is already in LDAP
|
||||||
|
return True
|
||||||
|
# User not in LDAP, so just close it down, write the log and return False
|
||||||
|
else:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Info: UserLookUp] Searched for %s and not found it.\n' % (datetime.now(), LDAP_UID) )
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Create a user in the LDAP. Assumes the checks are already in place for existing users
|
||||||
|
class CreateUser(object):
|
||||||
|
name = "createuser"
|
||||||
|
dispatch = EventDispatcher()
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
def create_user(self, user, password, firstname, lastname, email):
|
||||||
|
# Creates a user with some basic data
|
||||||
|
server = ldapservers()
|
||||||
|
conn = Connection(server, config['LDAP']['LDAPMANAGER'], config.get('LDAP','LDAPMANAGERPASSWORD', raw=True))
|
||||||
|
if not conn.bind():
|
||||||
|
self.dispatch('ldap', '%s [Error CreateUser] Could not connect to LDAPserver\n' % datetime.now() )
|
||||||
|
return "Could not connect to LDAP Server."
|
||||||
|
|
||||||
|
# set objectClasses for the new user
|
||||||
|
obj_new_user = ObjectDef(['inetOrgPerson', 'posixAccount', 'ldapPublicKey'], conn)
|
||||||
|
w = Writer(conn, obj_new_user)
|
||||||
|
dn = 'uid=%s,ou=users,dc=ungleich,dc=ch' % user
|
||||||
|
w.new(dn)
|
||||||
|
# Filling in some of the data
|
||||||
|
# required attributes are sn, cn, homeDirectory, uid (already handled by dn), uidNumber, gidNumber
|
||||||
|
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/%s' % user
|
||||||
|
# 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
|
||||||
|
if not w.commit():
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error CreateUser] Could not write new user %s to LDAP DB\n' % (datetime.now(), dn) )
|
||||||
|
return "Couldn't write data to the LDAP Server."
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Info CreateUser] %s created.\n' % (datetime.now(), dn) )
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
conn.search('dc=ungleich,dc=ch', '(&(objectClass=posixAccount)(uidNumber=*))', attributes = [ 'uidNumber' ])
|
||||||
|
newuid = 0
|
||||||
|
uidlist = []
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Returns some basic data from an user
|
||||||
|
class GetUserData(object):
|
||||||
|
name = "getuserdata"
|
||||||
|
dispatch = EventDispatcher()
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
def get_data(self, user):
|
||||||
|
# Setup the search parameter and connect to LDAP
|
||||||
|
LDAP_UID = 'uid=%s' % user
|
||||||
|
server = ldapservers()
|
||||||
|
conn = Connection(server, config['LDAP']['LDAPMANAGER'], config.get('LDAP', 'LDAPMANAGERPASSWORD', raw=True))
|
||||||
|
conn.bind()
|
||||||
|
if not conn.bound:
|
||||||
|
self.dispatch('ldap', '%s [Error GetUserData] Could not connect to LDAP server.\n' % datetime.now() )
|
||||||
|
return ("error", "Could not connect to LDAP server.", "", "")
|
||||||
|
rdn = user_or_customer(LDAP_UID)
|
||||||
|
if rdn == False:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Info GetUserData] Could not find user %s\n' % (datetime.now(), LDAP_UID) )
|
||||||
|
return ("error", "Could not find the user.", "", "")
|
||||||
|
# Workaround because not all users have the same objectClasses
|
||||||
|
objclass = objclasses(rdn, LDAP_UID, conn)
|
||||||
|
obj = ObjectDef(objclass, conn)
|
||||||
|
# The Reader gets the data for the user
|
||||||
|
r = Reader(conn, obj, rdn)
|
||||||
|
r.search()
|
||||||
|
# Since the DN is basically confirmed by user_or_customer() it shouldn't throw an exception, but better check
|
||||||
|
try:
|
||||||
|
x = r[0].sn
|
||||||
|
except:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error GetUserData] Could not open Reader for %s\n' % (datetime.now(), rdn) )
|
||||||
|
return ("error", "Could not read data for user.", "", "")
|
||||||
|
# Putting the results into strings and then clean it up a bit if some attribute is not set in LDAP
|
||||||
|
(firstname, lastname, email) = (str(r[0].givenName), str(r[0].sn), str(r[0].mail))
|
||||||
|
if firstname == '[]':
|
||||||
|
firstname = 'No firstname given'
|
||||||
|
if lastname == '[]':
|
||||||
|
lastname = 'No lastname given'
|
||||||
|
if email == '[]':
|
||||||
|
email = 'No email given'
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Info GetUserData] Got data for %s Firstname: %s Lastname: %s Email: %s\n' % (datetime.now(), rdn, firstname, lastname, email) )
|
||||||
|
return ("OK", firstname, lastname, email)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# change some (firstname, lastname, email) data for the user
|
||||||
|
class ChangeUserData(object):
|
||||||
|
name = "changeuserdata"
|
||||||
|
dispatch = EventDispatcher()
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
def change_data(self, user, firstname, lastname, email):
|
||||||
|
LDAP_UID = 'uid=%s' % user
|
||||||
|
server = ldapservers()
|
||||||
|
# Establish connection with a user who can change the data
|
||||||
|
conn = Connection(server, config['LDAP']['LDAPMANAGER'], config.get('LDAP', 'LDAPMANAGERPASSWORD', raw=True))
|
||||||
|
if not conn.bind():
|
||||||
|
self.dispatch('ldap', '%s [Error ChangeUserData] Could not connect to LDAP server.\n' % datetime.now() )
|
||||||
|
return "Could not connect to LDAP server."
|
||||||
|
# get the DN of the user
|
||||||
|
rdn = user_or_customer(LDAP_UID)
|
||||||
|
if rdn == False:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Info ChangeUserData] User with %s not found.\n' % (datetime.now(), LDAP_UID) )
|
||||||
|
return "Could not find user."
|
||||||
|
# Fix because not every user has the same objectClasses
|
||||||
|
objclass = objclasses(rdn, LDAP_UID, conn)
|
||||||
|
# Set up a reader for the user
|
||||||
|
obj = ObjectDef(objclass, conn)
|
||||||
|
r = Reader(conn, obj, rdn)
|
||||||
|
r.search()
|
||||||
|
# Again, user_or_customer() should prevent it from throwing an exception because it's a confirmed user
|
||||||
|
try:
|
||||||
|
x = r[0].sn
|
||||||
|
except:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error ChangeUserData] Could not open Reader for %s\n' % (datetime.now(), rdn) )
|
||||||
|
return "Could not open the data of user."
|
||||||
|
# Opens a Writer instance prefilled with the old data
|
||||||
|
# We could check if something has changed, but since the form takes the old data as standard values, let's
|
||||||
|
# just update the relevant attributes
|
||||||
|
w = Writer.from_cursor(r)
|
||||||
|
w[0].sn = lastname
|
||||||
|
w[0].cn = firstname + " " + lastname
|
||||||
|
w[0].givenName = firstname
|
||||||
|
w[0].mail = email
|
||||||
|
# check if the data is written
|
||||||
|
if not w.commit():
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error ChangeUserData] Could not write changes for %s\n' % (datetime.now(), rdn) )
|
||||||
|
return "Could not write changes for user."
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Info ChangeUserData] Changed data for %s Firstname: %s Lastname: %s Email: %s\n' % (datetime.now(), rdn, firstname, lastname, email) )
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# change the password for the user
|
||||||
|
class ChangePassword(object):
|
||||||
|
name = "changepassword"
|
||||||
|
dispatch = EventDispatcher()
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
def change_password(self, user, newpassword):
|
||||||
|
LDAP_UID = 'uid=%s' % user
|
||||||
|
server = ldapservers()
|
||||||
|
conn = Connection(server, config['LDAP']['LDAPMANAGER'], config.get('LDAP', 'LDAPMANAGERPASSWORD', raw=True))
|
||||||
|
if not conn.bind():
|
||||||
|
self.dispatch('ldap', '%s [Error ChangePassword] Could not connect to LDAP server.\n' % datetime.now() )
|
||||||
|
return "Could not connect to LDAP server."
|
||||||
|
# check if uid=user is in either ou=customers or ou=users
|
||||||
|
rdn = user_or_customer(LDAP_UID)
|
||||||
|
if rdn == False:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error ChangePassword] Could not find user %s\n' % (datetime.now(), LDAP_UID) )
|
||||||
|
return "Could not find the user."
|
||||||
|
# Plus not everyone has the same objectClasses, so workaround
|
||||||
|
objclass = objclasses(rdn, LDAP_UID, conn)
|
||||||
|
obj = ObjectDef(objclass, conn)
|
||||||
|
# Set up a Reader for the DN
|
||||||
|
r = Reader(conn, obj, rdn)
|
||||||
|
r.search()
|
||||||
|
# Shouldn't throw an exception, since the user is confirmed to be there
|
||||||
|
try:
|
||||||
|
x = r[0].sn
|
||||||
|
except:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error ChangePassword] Could not open Reader for %s\n' % (datetime.now(), rdn) )
|
||||||
|
return "Could not open the data for the user."
|
||||||
|
# Set up the writer and overwrite the attribute with the new password
|
||||||
|
w = Writer.from_cursor(r)
|
||||||
|
w[0].userPassword = newpassword
|
||||||
|
# Check to see if the change has gone through
|
||||||
|
if not w.commit():
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error ChangePassword] Could not write data for %s\n' % (datetime.now(), rdn) )
|
||||||
|
return "Could not write data for the user."
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Info ChangePassword] Password changed for %s\n' % (datetime.now(), rdn) )
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# Deletes a user from LDAP
|
||||||
|
class DeleteUser(object):
|
||||||
|
name = "deleteuser"
|
||||||
|
dispatch = EventDispatcher()
|
||||||
|
|
||||||
|
@rpc
|
||||||
|
def delete_user(self, user):
|
||||||
|
LDAP_UID = 'uid=%s' % user
|
||||||
|
server = ldapservers()
|
||||||
|
conn = Connection(server, config['LDAP']['LDAPMANAGER'], config.get('LDAP', 'LDAPMANAGERPASSWORD', raw=True))
|
||||||
|
conn.bind()
|
||||||
|
if not conn.bound:
|
||||||
|
self.dispatch('ldap', '%s [Error DeleteUser] Could not connect to LDAP server.\n' % datetime.now() )
|
||||||
|
return "Could not connect to LDAP server."
|
||||||
|
# again, check whether the uid= is in ou=users or ou=customers
|
||||||
|
dn = user_or_customer(LDAP_UID)
|
||||||
|
if dn == False:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error DeleteUser] Could not find the user %s\n' % (datetime.now(), LDAP_UID) )
|
||||||
|
return "Could not find the user."
|
||||||
|
# Check if the delete was successfull
|
||||||
|
deleted = conn.delete(dn)
|
||||||
|
if not deleted:
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Error DeleteUser] Could not delete %s\n' % (datetime.now(), dn) )
|
||||||
|
return "Could not delete the user."
|
||||||
|
conn.unbind()
|
||||||
|
self.dispatch('ldap', '%s [Info DeleteUser] Deleted %s\n' % (datetime.now(), dn) )
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# the class to log all the dispatches
|
||||||
|
# for now everything gets logged into the same logfile, but
|
||||||
|
# I don't forsee that much traffic plus with timestamps and the class name
|
||||||
|
# in the log should be readable
|
||||||
|
class Log(object):
|
||||||
|
name = "log"
|
||||||
|
ldaplog = config['System']['LOGDIR'] + '/ldap.log'
|
||||||
|
|
||||||
|
|
||||||
|
# Gets all the dispatches with 'ldap' and writes them into the ldap.log
|
||||||
|
@event_handler('userlookup', 'ldap')
|
||||||
|
@event_handler('createuser', 'ldap')
|
||||||
|
@event_handler('getuserdata', 'ldap')
|
||||||
|
@event_handler('changeuserdata', 'ldap')
|
||||||
|
@event_handler('passwordresetrequest', 'ldap')
|
||||||
|
@event_handler('changepassword', 'ldap')
|
||||||
|
@event_handler('deleteuser', 'ldap')
|
||||||
|
def event_handler_ldap(self, payload):
|
||||||
|
f = open(self.ldaplog, mode='a', encoding='utf-8')
|
||||||
|
f.write(payload)
|
||||||
|
f.close
|
||||||
|
|
Loading…
Reference in a new issue