From 67f3fefae517e39db6badcdf722ec749c4d6c444 Mon Sep 17 00:00:00 2001 From: fram3d Date: Thu, 28 Mar 2024 17:54:13 +0100 Subject: [PATCH] fix bugs --- build-deb/Makefile | 4 +- build-deb/luser/DEBIAN/control | 2 +- luser/models.py | 144 ++++++++++++++++++++------------- 3 files changed, 89 insertions(+), 61 deletions(-) diff --git a/build-deb/Makefile b/build-deb/Makefile index 3f871b6..4364346 100644 --- a/build-deb/Makefile +++ b/build-deb/Makefile @@ -1,10 +1,12 @@ all: man deb man: man/luser.1.md + mkdir -p luser/usr/share/man/man1/ pandoc man/luser.1.md -f markdown+hard_line_breaks -s -t man -o man/luser.1 cp man/luser.1 luser/usr/share/man/man1/ gzip -f luser/usr/share/man/man1/luser.1 deb: man ../requirments.txt ../run.py ../luser ../LICENSE + mkdir -p luser/var/luser/luser cp -r ../luser/* luser/var/luser/luser/ cp ../run.py luser/var/luser/ cp ../LICENSE luser/var/luser/ @@ -16,5 +18,3 @@ clean: rm -f luser.deb rm -f man/luser.1 rm -rf luser/var - mkdir -p luser/var/luser/luser - mkdir -p luser/usr/share/man/man1/ diff --git a/build-deb/luser/DEBIAN/control b/build-deb/luser/DEBIAN/control index 40eeb82..6717c61 100644 --- a/build-deb/luser/DEBIAN/control +++ b/build-deb/luser/DEBIAN/control @@ -8,4 +8,4 @@ Depends: python3-flask, python3-ldap3, gunicorn, imagemagick, python3-passlib Homepage: https://gitea.dmz.rs/fram3d/luser Maintainer: fram3d Description: Web app that allows users to add,remove and change passwords in LDAP system -Version: 1.0.7 +Version: 1.0.8 diff --git a/luser/models.py b/luser/models.py index 7a6f8a3..6178a28 100644 --- a/luser/models.py +++ b/luser/models.py @@ -1,3 +1,4 @@ +import ldap3 from ldap3 import Server,Connection,ALL,MODIFY_REPLACE from datetime import datetime @@ -9,9 +10,30 @@ class LUSER(): admin_user := string DN of LDAP admin user admin_pass := string password of LDAP admin user base := string base in LDAP system where users are made - basealt := string base in LDAP system where users are made with password hashes generated for openalt ''' + def getlastlog(self)->int: + self.ldapconnection.search(search_base=f'uid=total,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) + response = self.ldapconnection.response + if response == []: + response = 0 + else: + response = int(response['attributes']['uidNumber']) + return response + + def setlastlog(self, newvalue: int): + newvalue = int(newvalue) + + # Check if total record already present + self.ldapconnection.search(search_base=f'uid=total,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) + response = self.ldapconnection.response + + if response == []: + self.ldapconnection.add(f'uid=total,{self.logbase}', OBJECTCLASSES, { 'uid' : 'total', 'uidNumber' : 0 }) + + self.ldapconnection.modify(f'uid=total,{self.logbase}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])}) + + return self.ldapconnection.response def findlastuid(self): ''' @@ -33,16 +55,12 @@ class LUSER(): return max - def expandbase(self, basealt = ''): + def expandbase(self): ''' Extract orgnaization, name of dc object and full domain part with all dc values from base - basealt := string base in LDAP system where users are made, if not set the function uses base specified on creation of LUSER instance (self.base) ''' # Split base string with commas to find values of organization and dc - if basealt == '': - baselist = self.base.split(",") - else: - baselist = self.basealt.split(",") + baselist = self.base.split(",") organization = '' dc = '' @@ -73,15 +91,13 @@ class LUSER(): return organization, dc, dcfull, domain - def __init__(self, ldap_host, admin_user, admin_pass, base, basealt='', autoconnect=True, lastUID = 1000): + def __init__(self, ldap_host, admin_user, admin_pass, base, autoconnect=True, lastUID = 1000): self.ldap_host = ldap_host self.admin_user = admin_user self.admin_pass = admin_pass self.base = base - self.basealt = basealt self.organization, self.dc, self.dcfull, self.domain = self.expandbase() - self.organizationalt, self.dcalt, self.dcfullalt, self.domainalt = self.expandbase(self.basealt) - self.alt = True + self.logbase = f'ou=log,{self.dcfull}' self.autoconnect = autoconnect ldapserver = Server(ldap_host, use_ssl=True) lastuidfound = 0 @@ -92,6 +108,15 @@ class LUSER(): else: self.ldapconnection = Connection(ldapserver, admin_user, admin_pass, auto_bind=False) + # Check if base and log base is created + self.ldapconnection.search(search_base=f'{self.base}',search_filter='(objectClass=organizationalUnit)', attributes=['ou']) + if self.ldapconnection.response == []: + self.prepare() + + self.ldapconnection.search(search_base=f'{self.logbase}',search_filter='(objectClass=organizationalUnit)', attributes=['ou']) + if self.ldapconnection.response == []: + self.prepare() + if lastuidfound == 0: self.lastuid = lastUID self.lastgid = lastUID @@ -99,31 +124,21 @@ class LUSER(): self.lastuid = lastuidfound self.lastgid = lastuidfound - - # Set alt boolean to false if basealt not set - if basealt == '': - self.alt = False - def prepare(self): ''' Create base on LDAP host ''' # Create dcObject on LDAP server and store boolean indicating it's success - rcode1 = self.ldapconnection.add(f'dc={self.dcfull}', ['dcObject', 'organization'], {'o' : self.dc, 'dc' : self.dc}) + rcode1 = self.ldapconnection.add(self.dcfull, ['dcObject', 'organization'], {'o' : self.dc, 'dc' : self.dc}) # Create organizational units on LDAP server and store boolean indicating it's success - rcode2 = self.ldapconnection.add(self.base, ['top', 'organizationalUnit'], {'ou' : self.organization}) - # Add dcobject and organizational units as above for base alt - rcode3 = True - rcode4 = True - if self.alt: - rcode3 = self.ldapconnection.add(f'dc={self.dcfull}', ['dcObject', 'organization'], {'o' : self.dc, 'dc' : self.dc}) - rcode4 = self.ldapconnection.add(self.base, ['top', 'organizationalUnit'], {'ou' : self.organization}) + # Create organizational units for log on LDAP server and store boolean indicating it's success + rcode3 = self.ldapconnection.add(self.logbase, ['top', 'organizationalUnit'], {'ou' : self.organization}) - return rcode1 and rcode2 and rcode3 and rcode4 + return rcode1 and rcode2 and rcode3 def lastpwchangenow(self): ''' @@ -133,12 +148,11 @@ class LUSER(): return str((datetime.utcnow() - datetime(1970,1,1)).days) - def add(self, user, password, althash=""): + def add(self, user, password): ''' Add a user to base in LDAP with user and pass as credentials user := string containing username password := string containing user password - althash := string containing user password/hash for the alternative base ''' # Increase UID and GID counters @@ -154,16 +168,17 @@ class LUSER(): # Attributes for a user entry attributes = {'cn' : user, 'sn' : user, 'givenName' : user, 'uid' : user, 'uidNumber' : self.lastuid, 'gidNumber' : self.lastgid, 'homeDirectory' : f'/home/{user}', 'loginShell' : '/usr/bin/git-shell', 'gecos' : 'SystemUser', 'shadowLastChange' : self.lastpwchangenow(), 'shadowMax' : '45', 'userPassword' : password, 'mail' : f'{user}@{self.domain}' } - attributesalt = {'cn' : user, 'sn' : user, 'givenName' : user, 'uid' : user, 'uidNumber' : self.lastuid, 'gidNumber' : self.lastgid, 'homeDirectory' : f'/home/{user}', 'loginShell' : '/usr//bin/git-shell', 'gecos' : 'SystemUser', 'shadowLastChange' : self.lastpwchangenow(), 'shadowMax' : '45', 'userPassword' : althash, 'mail' : f'{user}@{self.domainalt}'} - # Return boolean value of new user entry - rcode1 = self.ldapconnection.add(f'{id},{self.base}', objectClass, attributes) + rcode1 = self.ldapconnection.add(self.logbase, objectClass, attributes) - # If alternative base is set add the same user to the alternative base as well, if not act as if it was success - if self.alt: - rcode2 = self.ldapconnection.add(f'{id},{self.basealt}', objectClass, attributesalt) - else: - rcode2 = True + # Add new user to log + attributes['description'] = 'ADD' + lastlog = self.getlastlog() + 1 + + rcode2 = self.ldapconnection.add(f'uid={lastlog},{self.logbase}', objectClass, attributes) + + if rcode2: + self.setlastlog(lastlog) # Return True only if both entries was successful return rcode1 and rcode2 @@ -174,29 +189,34 @@ class LUSER(): user := string containing username newpass := string containing new password - althash := string containing password/hash for alternative base ''' - # This variable holds boolean indicating successful change of user password - chpassbool = self.ldapconnection.modify(f'uid={user},{self.base}', {'userPassword': (MODIFY_REPLACE,[newpass])}) - - # If alternative base is set modify user password/hash with althash, if not, pretend change was successful - if self.alt: - chpassboolalt = self.ldapconnection.modify(f'uid={user},{self.basealt}', {'userPassword': (MODIFY_REPLACE,[althash])}) - else: - chpassboolalt = True + chpassbool = False # This variable holds boolean indicating successful change of shadowLastChange value to current time - chlastchangebool = self.ldapconnection.modify(f'uid={user},{self.base}', {'shadowLastChange' : (MODIFY_REPLACE,[self.lastpwchangenow()])}) + chlastchangebool = False - # If alternative base is set modify user password/hash with althash, if not, pretend change was successful - if self.alt: - chlastchangeboolalt = self.ldapconnection.modify(f'uid={user},{self.base}', {'shadowLastChange' : (MODIFY_REPLACE, [self.lastpwchangenow()])}) - else: - chlastchangeboolalt = True + USERATTRIBUTES=['cn' , 'sn', 'givenName', 'uid', 'uidNumber' , 'gidNumber', 'homeDirectory', 'loginShell', 'gecos' , 'shadowLastChange', 'shadowMax', 'userPassword', 'mail'] + + OBJECTCLASSES = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount'] + + self.ldapconnection.search(search_base=f'uid=username,{self.base}',search_filter='(objectClass=person)', attributes=USERATTRIBUTES) + + userdata = self.ldapconnection.response[0] + userdata['attributes']['description'] = 'CHANGEPASS' + + lastlog = self.getlastlog() + 1 + + rcode1 = self.ldapconnection.add(f'uid={lastlog},{self.logbase}', OBJECTCLASSES, userdata['attributes']) + + if rcode1: + self.setlastlog(lastlog) + chpassbool = self.ldapconnection.modify(f'uid={user},{self.base}', {'userPassword': (MODIFY_REPLACE,[newpass])}) + + chlastchangebool = self.ldapconnection.modify(f'uid={user},{self.base}', {'shadowLastChange' : (MODIFY_REPLACE,[self.lastpwchangenow()])}) # Return True only if changing of both password and time of last password change was successful - return chpassbool and chpassboolalt and chlastchangebool and chlastchangeboolalt + return chpassbool and chlastchangebool def delete(self, user): ''' @@ -205,15 +225,23 @@ class LUSER(): user := string containing username ''' - rcode1 = self.ldapconnection.delete(f'uid={user},{self.base}') + USERATTRIBUTES=['cn' , 'sn', 'givenName', 'uid', 'uidNumber' , 'gidNumber', 'homeDirectory', 'loginShell', 'gecos' , 'shadowLastChange', 'shadowMax', 'userPassword', 'mail'] - # If alternative base is set delete user from alternative base, if not, pretend deletion was successful - if self.alt: - rcode2 = self.ldapconnection.delete(f'uid={user},{self.basealt}') - else: - rcode2 = True + OBJECTCLASSES = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount'] + + self.ldapconnection.search(search_base=f'uid={user},{self.base}',search_filter='(objectClass=person)', attributes=USERATTRIBUTES) + + userdata = self.ldapconnection.response[0] + userdata['attributes']['description'] = 'DELETE' + + lastlog = self.getlastlog() + 1 + + rcode1 = self.ldapconnection.add(f'uid={lastlog},{self.logbase}', OBJECTCLASSES, userdata['attributes']) + + if rcode1: + self.setlastlog(lastlog) + rcode2 = self.ldapconnection.delete(f'uid={user},{self.base}') - # Return True only if deletion from both bases was successful return rcode1 and rcode2 def getpassword(self, user):