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 all: man deb
man: man/luser.1.md 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 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/ cp man/luser.1 luser/usr/share/man/man1/
gzip -f luser/usr/share/man/man1/luser.1 gzip -f luser/usr/share/man/man1/luser.1
deb: man ../requirments.txt ../run.py ../luser ../LICENSE deb: man ../requirments.txt ../run.py ../luser ../LICENSE
mkdir -p luser/var/luser/luser
cp -r ../luser/* luser/var/luser/luser/ cp -r ../luser/* luser/var/luser/luser/
cp ../run.py luser/var/luser/ cp ../run.py luser/var/luser/
cp ../LICENSE luser/var/luser/ cp ../LICENSE luser/var/luser/
@ -16,5 +18,3 @@ clean:
rm -f luser.deb rm -f luser.deb
rm -f man/luser.1 rm -f man/luser.1
rm -rf luser/var 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 Homepage: https://gitea.dmz.rs/fram3d/luser
Maintainer: fram3d <fram3d@dmz.rs> Maintainer: fram3d <fram3d@dmz.rs>
Description: Web app that allows users to add,remove and change passwords in LDAP system 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 ldap3 import Server,Connection,ALL,MODIFY_REPLACE
from datetime import datetime from datetime import datetime
@ -9,9 +10,30 @@ class LUSER():
admin_user := string DN of LDAP admin user admin_user := string DN of LDAP admin user
admin_pass := string password of LDAP admin user admin_pass := string password of LDAP admin user
base := string base in LDAP system where users are made 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): def findlastuid(self):
''' '''
@ -33,16 +55,12 @@ class LUSER():
return max 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 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 # Split base string with commas to find values of organization and dc
if basealt == '': baselist = self.base.split(",")
baselist = self.base.split(",")
else:
baselist = self.basealt.split(",")
organization = '' organization = ''
dc = '' dc = ''
@ -73,15 +91,13 @@ class LUSER():
return organization, dc, dcfull, domain 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.ldap_host = ldap_host
self.admin_user = admin_user self.admin_user = admin_user
self.admin_pass = admin_pass self.admin_pass = admin_pass
self.base = base self.base = base
self.basealt = basealt
self.organization, self.dc, self.dcfull, self.domain = self.expandbase() self.organization, self.dc, self.dcfull, self.domain = self.expandbase()
self.organizationalt, self.dcalt, self.dcfullalt, self.domainalt = self.expandbase(self.basealt) self.logbase = f'ou=log,{self.dcfull}'
self.alt = True
self.autoconnect = autoconnect self.autoconnect = autoconnect
ldapserver = Server(ldap_host, use_ssl=True) ldapserver = Server(ldap_host, use_ssl=True)
lastuidfound = 0 lastuidfound = 0
@ -92,6 +108,15 @@ class LUSER():
else: else:
self.ldapconnection = Connection(ldapserver, admin_user, admin_pass, auto_bind=False) 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: if lastuidfound == 0:
self.lastuid = lastUID self.lastuid = lastUID
self.lastgid = lastUID self.lastgid = lastUID
@ -99,31 +124,21 @@ class LUSER():
self.lastuid = lastuidfound self.lastuid = lastuidfound
self.lastgid = lastuidfound self.lastgid = lastuidfound
# Set alt boolean to false if basealt not set
if basealt == '':
self.alt = False
def prepare(self): def prepare(self):
''' '''
Create base on LDAP host Create base on LDAP host
''' '''
# Create dcObject on LDAP server and store boolean indicating it's success # 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 # Create organizational units on LDAP server and store boolean indicating it's success
rcode2 = self.ldapconnection.add(self.base, ['top', 'organizationalUnit'], {'ou' : self.organization}) rcode2 = self.ldapconnection.add(self.base, ['top', 'organizationalUnit'], {'ou' : self.organization})
# Add dcobject and organizational units as above for base alt # Create organizational units for log on LDAP server and store boolean indicating it's success
rcode3 = True rcode3 = self.ldapconnection.add(self.logbase, ['top', 'organizationalUnit'], {'ou' : self.organization})
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})
return rcode1 and rcode2 and rcode3 and rcode4 return rcode1 and rcode2 and rcode3
def lastpwchangenow(self): def lastpwchangenow(self):
''' '''
@ -133,12 +148,11 @@ class LUSER():
return str((datetime.utcnow() - datetime(1970,1,1)).days) 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 Add a user to base in LDAP with user and pass as credentials
user := string containing username user := string containing username
password := string containing user password password := string containing user password
althash := string containing user password/hash for the alternative base
''' '''
# Increase UID and GID counters # Increase UID and GID counters
@ -154,16 +168,17 @@ class LUSER():
# Attributes for a user entry # 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}' } 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 # 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 # Add new user to log
if self.alt: attributes['description'] = 'ADD'
rcode2 = self.ldapconnection.add(f'{id},{self.basealt}', objectClass, attributesalt) lastlog = self.getlastlog() + 1
else:
rcode2 = True 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 True only if both entries was successful
return rcode1 and rcode2 return rcode1 and rcode2
@ -174,29 +189,34 @@ class LUSER():
user := string containing username user := string containing username
newpass := string containing new password newpass := string containing new password
althash := string containing password/hash for alternative base
''' '''
# This variable holds boolean indicating successful change of user password # This variable holds boolean indicating successful change of user password
chpassbool = self.ldapconnection.modify(f'uid={user},{self.base}', {'userPassword': (MODIFY_REPLACE,[newpass])}) chpassbool = False
# 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
# This variable holds boolean indicating successful change of shadowLastChange value to current time # 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 USERATTRIBUTES=['cn' , 'sn', 'givenName', 'uid', 'uidNumber' , 'gidNumber', 'homeDirectory', 'loginShell', 'gecos' , 'shadowLastChange', 'shadowMax', 'userPassword', 'mail']
if self.alt:
chlastchangeboolalt = self.ldapconnection.modify(f'uid={user},{self.base}', {'shadowLastChange' : (MODIFY_REPLACE, [self.lastpwchangenow()])}) OBJECTCLASSES = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount']
else:
chlastchangeboolalt = True 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 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): def delete(self, user):
''' '''
@ -205,15 +225,23 @@ class LUSER():
user := string containing username 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 OBJECTCLASSES = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount']
if self.alt:
rcode2 = self.ldapconnection.delete(f'uid={user},{self.basealt}') self.ldapconnection.search(search_base=f'uid={user},{self.base}',search_filter='(objectClass=person)', attributes=USERATTRIBUTES)
else:
rcode2 = True 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 return rcode1 and rcode2
def getpassword(self, user): def getpassword(self, user):