This commit is contained in:
fram3d 2024-03-28 17:54:13 +01:00
parent e6fdb916bf
commit 67f3fefae5
Signed by: fram3d
GPG Key ID: 938920E709EEA32A
3 changed files with 89 additions and 61 deletions

View File

@ -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/

View File

@ -8,4 +8,4 @@ Depends: python3-flask, python3-ldap3, gunicorn, imagemagick, python3-passlib
Homepage: https://gitea.dmz.rs/fram3d/luser
Maintainer: fram3d <fram3d@dmz.rs>
Description: Web app that allows users to add,remove and change passwords in LDAP system
Version: 1.0.7
Version: 1.0.8

View File

@ -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(",")
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 = False
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()])})
# 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
# 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):