restrucute code

This commit is contained in:
fram3d 2024-04-05 13:50:34 +02:00
parent 13518fafa9
commit 80a64511cd
Signed by: fram3d
GPG Key ID: 938920E709EEA32A

355
models.py
View File

@ -1,36 +1,31 @@
import ldap3 import ldap3
import random import random
from datetime import datetime
MAXENTRIES = 1000000 MAXENTRIES = 1000000
OBJECTCLASSES = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount'] OBJECTCLASSES = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount']
USERATTRIBUTES = ['cn' , 'sn', 'givenName', 'uid', 'uidNumber' , 'gidNumber', 'homeDirectory', 'loginShell', 'gecos' , 'shadowLastChange', 'shadowMax', 'userPassword', 'mail', 'description'] USERATTRIBUTES = ['cn' , 'sn', 'givenName', 'uid', 'uidNumber' , 'gidNumber', 'homeDirectory', 'loginShell', 'gecos' , 'shadowLastChange', 'shadowMax', 'userPassword', 'mail', 'description']
class ldapbind():
def __init__(self, ldap_host: str, admin_user: str, admin_pass: str, base: str): class logserver():
self.ldap_host = ldap_host def __init__(self, _host: str, admin_user: str, admin_pass: str, base: str, ssl: bool = True ):
self._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.ssl = ssl
if ssl:
server = ldap3.Server(self.ldap_host,use_ssl=True)
else:
server = ldap3.Server(self.ldap_host)
ldapserver = ldap3.Server(self.ldap_host,use_ssl=True)
self.conn = ldap3.Connection(ldapserver, admin_user, admin_pass, auto_bind=True) self.conn = ldap3.Connection(ldapserver, admin_user, admin_pass, auto_bind=True)
class logbranch(): self.organization, self.dc, self.dcfull, self.domain = self.expandbase()
def __init__(self, ldap_server: ldapbind, log_server: ldapbind = None):
self.ldap_server = ldap_server self.logbase = f'ou=log,{self.dc}'
self.log_server = log_server
if self.log_server == None:
dc = ldap_server.base.split(',')
self.dc = ','.join(dc[1:])
self.log_server = ldapbind(ldap_server.ldap_host, ldap_server.admin_user, ldap_server.admin_pass, f'ou=log,{self.dc}')
self.base = self.ldap_server.base
self.logbase = self.log_server.base
self.ldapconnection = self.ldap_server.conn
self.logconnection = self.log_server.conn
# Unique id of the log branch # Unique id of the log branch
self.id = self.getid() self.id = self.getid()
@ -41,145 +36,287 @@ class logbranch():
# How many changes are recoreded in total # How many changes are recoreded in total
self.total = self.gettotal() self.total = self.gettotal()
def getid(self)->int: def expandbase(self, base: str = '')->(str,str,str,str):
self.logconnection.search(search_base=f'uid=id,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) '''
response = self.logconnection.response Extract orgnaization, name of dc object and full domain part with al
l dc values from base
'''
if base == '':
base = self.logbase
# Split base string with commas to find values of organization and dc
baselist = base.split(",")
organization = ''
dc = ''
dcfull = ''
domain = ''
# Find ou in base and set it as organization variable
for i in baselist:
if i.split('=')[0] == 'ou':
organization = i.split('=')[1]
# Find first dc and set it as dc variable
for i in baselist:
if i.split('=')[0] == 'dc':
dc = i.split('=')[1]
break
# Find full dc and set it as dcfull variable
for i in baselist:
if i.split('=')[0] == 'dc':
# if first dc, add it from dc variable
if dcfull == '' and domain == '':
dcfull = f'dc={dc}'
domain = dc
else:
dcfull += ',dc=' + i.split('=')[1]
domain += f'.{i.split("=")[1]}'
return organization, dc, dcfull, domain
def getid(self, base: str = '')->int:
if base == '':
base = self.logbase
self.conn.search(search_base=f'uid=id,{base}',search_filter='(objectClass=person)', attributes=['uidNumber'])
response = self.conn.response
if response == []: if response == []:
id = random.randint(10**12, 10**13-1) id = random.randint(10**12, 10**13-1)
self.logconnection.add(f'uid=id,{self.logbase}', OBJECTCLASSES, { 'uid' : 'id', 'uidNumber' : id }) attributes = {'cn' : 'id', 'sn' : 'id', 'givenName' : 'id', 'uid' : 'id', 'uidNumber' : id, 'gidNumber' : id, 'homeDirectory' : f'/home/id', 'loginShell' : '/usr/bin/git-shell', 'gecos' : 'SystemUser', 'shadowLastChange' : self.lastpwchangenow(), 'shadowMax' : '45', 'userPassword' : 'id', 'mail' : f'id@{self.domain}' }
self.conn.add(f'uid=id,{base}', OBJECTCLASSES, attributes)
return id return id
return int(response[0]['attributes']['uidNumber']) else:
response = int(response[0]['attributes']['uidNumber'])
def gettotal(self)->int: return response
self.logconnection.search(search_base=f'uid=total,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber'])
response = self.logconnection.response def gettotal(self, base: str = '')->int:
if base == '':
base = self.logbase
self.conn.search(search_base=f'uid=total,{base}',search_filter='(objectClass=person)', attributes=['uidNumber'])
response = self.conn.response
if response == []: if response == []:
settotal(0) response = 0
return 0 else:
return int(response[0]['attributes']['uidNumber']) response = int(response[0]['attributes']['uidNumber'])
return response
def settotal(self, newvalue: int, base: str = ''):
if base == '':
base = self.logbase
def settotal(self, newvalue: int):
newvalue = int(newvalue) newvalue = int(newvalue)
# Check if total record already present # Check if total record already present
self.logconnection.search(search_base=f'uid=total,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) self.conn.search(search_base=f'uid=total,{base}',search_filter='(objectClass=person)', attributes=['uidNumber'])
response = self.logconnection.response response = self.conn.response
attributes = {'cn' : 'total', 'sn' : 'total', 'givenName' : 'total', 'uid' : 'total', 'uidNumber' : newvalue, 'gidNumber' : newvalue, 'homeDirectory' : f'/home/total', 'loginShell' : '/usr/bin/git-shell', 'gecos' : 'SystemUser', 'shadowLastChange' : self.lastpwchangenow(), 'shadowMax' : '45', 'userPassword' : 'total', 'mail' : f'total@{self.domain}' }
if response == []: if response == []:
self.logconnection.add(f'uid=total,{self.logbase}', OBJECTCLASSES, { 'uid' : 'total', 'uidNumber' : 0 }) self.conn.add(f'uid=total,{base}', OBJECTCLASSES, attributes)
else:
self.conn.modify(f'uid=total,{base}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])})
self.logconnection.modify(f'uid=total,{self.logbase}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])}) return self.conn.response
return self.logconnection.response
def getloaded(self): def getloaded(self, base: str = '')->int:
self.connection.search(search_base=f'uid=loaded,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) if base == '':
response = self.logconnection.response base = self.logbase
self.conn.search(search_base=f'uid=loaded,{base}',search_filter='(objectClass=person)', attributes=['uidNumber'])
response = self.conn.response
if response == []: if response == []:
setloaded(0) response = 0
return 0 else:
return int(self.connection.response[0]['attributes']['uidNumber']) response = int(response[0]['attributes']['uidNumber'])
return response
def setloaded(self, newvalue: int, base: str = ''):
if base == '':
base = self.logbase
def setloaded(self, newvalue: int):
newvalue = int(newvalue) newvalue = int(newvalue)
# Check if loaded record already present # Check if loaded record already present
self.logconnection.search(search_base=f'uid=loaded,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) self.conn.search(search_base=f'uid=loaded,{base}',search_filter='(objectClass=person)', attributes=['uidNumber'])
response = self.logconnection.response response = self.conn.response
attributes = {'cn' : 'loaded', 'sn' : 'loaded', 'givenName' : 'loaded', 'uid' : 'loaded', 'uidNumber' : newvalue, 'gidNumber' : newvalue, 'homeDirectory' : f'/home/loaded', 'loginShell' : '/usr/bin/git-shell', 'gecos' : 'SystemUser', 'shadowLastChange' : self.lastpwchangenow(), 'shadowMax' : '45', 'userPassword' : 'loaded', 'mail' : f'loaded@{self.domain}' }
if response == []: if response == []:
self.logconnection.add(f'uid=loaded,{self.logbase}', OBJECTCLASSES, { 'uid' : 'loaded', 'uidNumber' : 0 }) self.conn.add(f'uid=loaded,{base}', OBJECTCLASSES, attributes)
else:
self.conn.modify(f'uid=loaded,{base}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])})
self.logconnection.modify(f'uid=loaded,{self.logbase}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])}) return self.conn.response
return self.logconnection.response
def refreshtotal(self): def refreshtotal(self, base: str = '')->int:
self.settotal(self.findtotal()) if base == '':
return self.gettotal() base = self.logbase
def findtotal(self): self.settotal(self.findtotal(base))
for entry in range(self.gettotal() + 1, MAXENTRIES): return self.gettotal(base)
self.logconnection.search(search_base=f'uid={entry},{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber'])
if self.logconnection.response == []: def findtotal(self, base: str = '')->int:
if base == '':
base = self.logbase
for entry in range(self.gettotal(base) + 1, MAXENTRIES):
self.conn.search(search_base=f'uid={entry},{base}',search_filter='(objectClass=person)', attributes=['uidNumber'])
if self.conn.response == []:
return entry - 1 return entry - 1
def applylogs(self): def applylogs(self, base: str = '')->int:
self.refreshtotal() if base == '':
base = self.logbase
loaded = self.getloaded() + 1 self.refreshtotal(base)
total = self.gettotal() + 1
loaded = self.getloaded(base) + 1
total = self.gettotal(base) + 1
for log_number in range(loaded, total): for log_number in range(loaded, total):
self.setlog(log_number) self.applylog(log_number, base)
return total - loaded return total - loaded
def applylog(self, log_number: int): def applylog(self, log_number: int, base: str = ''):
log = self.getlog(log_number) if base == '':
return self.setlog(self, log) base = self.logbase
def getlog(self, log_number: int): log = self.getlog(log_number, base)
self.logconnection.search(search_base=f'uid={log_number},{self.logbase}',search_filter = '(objectClass=person)', attributes = USERATTRIBUTES)
return logconnection.response[0]
def setlog(self, log_number: int): attributes = log['attribtes']
log = self.getlog(log_number)
attributes = log['attributes']['description']
uid = attributes['uid'] uid = attributes['uid']
action = attributes['description'] action = attributes['description']
attributes['description'] = '' attributes['description'] = ''
if action == 'ADD': if action == 'ADD':
self.ldapconnection.add(f'uid={uid},{self.base}', OBJECTCLASSES, attributes) self.conn.add(f'uid={uid},{base}', OBJECTCLASSES, attributes)
elif action == 'DELETE': elif action == 'DELETE':
self.ldapconnection.delete(f'uid={uid},{self.base}') self.conn.delete(f'uid={uid},{base}')
elif action == 'CHANGEPASS': elif action == 'CHANGEPASS':
self.ldapconnection.modify(f'uid={uid},{self.base}', {'userPassword': (MODIFY_REPLACE,attributes['userPassword'])}) self.conn.modify(f'uid={uid},{base}', {'userPassword': (MODIFY_REPLACE,attributes['userPassword'])})
else: else:
return f'Error: Unrecognized action in log entry: {action}' return f'Error: Unrecognized action in log entry: {action}'
self.setloaded( self.getloaded() + 1 ) self.setloaded(self.getloaded() + 1, base)
return ldapconnection.response return self.conn.response
class logtree(): def getlog(self, log_number: int, base: str = ''):
if base == '':
base = self.logbase
self.conn.search(search_base=f'uid={log_number},{base}',search_filter = '(objectClass=person)', attributes = USERATTRIBUTES)
return self.conn.response[0]
def setlog(self, attributes: dict, base: str = '', log_number: int = -1):
if base == '':
base = self.logbase
if log_number == -1:
log_number = self.gettotal(base) + 1
self.conn.add(f'uid={log_number},{base}', OBJECTCLASSES, attributes)
return self.conn.response
def getbasecopy(self, remote)->str:
'''
Gets the base of a log copy of remote server on the local server
'''
remoteid = remote.getid()
localdcfull = self.dcfull()
basecopy = f'ou=sync-{remoteid},{localdcfull}'
# Check if it exists and create it if not
self.conn.search(search_base=basecopy,search_filter='(objectClass=organizationalUnit)', attributes=['ou'])
if self.conn.response == []:
self.conn.add(basecopy, ['top', 'organizationalUnit'], {'ou' : f'sync-{remoteid}'})
return basecopy
def pullfrom(self, source)->int:
basecopy = self.getbasecopy(source)
total_dest = self.gettotal(basecopy)
total_src = source.gettotal()
for log_number in range(total_dest + 1, total_src + 1):
log = source.getlog(log_number)
attributes = log['attributes']
self.setlog(attributes, basecopy)
return total_src - total_dest
def applyfrom(self, source)->int:
basecopy = self.getbasecopy(source)
appliedlogs = self.applylogs(basecopy)
return appliedlogs
def lastpwchangenow(self):
'''
Return time of last password change for the user set to current time
messured in days from UNIX epoch
'''
return str((datetime.utcnow() - datetime(1970,1,1)).days)
class ldapsync():
def __init__(self): def __init__(self):
self.branches = [] self.servers = []
def add(self, log_server: logbranch): def add(self, log_server: logserver)->list:
# Find ID of new branch self.servers.append(log_server)
id = log_server.logconnection.search(search_base=f'{log_server.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) return self.servers
remotebranches = [] def remove(self, log_server: logserver)->list:
for branch in self.branches: self.servers.remove(log_server)
# Create base for log copies of the new log branch to all existing ones return self.servers
branch.ldapconnection.search(search_base=f'ou=ldapsync{id},{branch.dc}',search_filter='(objectClass=organizationalUnit)', attributes=['ou'])
if branch.ldapconnection.response == []:
branch.ldapconnection.add(f'ou=ldapsync{id},{branch.dc}', ['top', 'organizationalUnit'], {'ou' : id})
remotebranch = logbranch(branch.log_server)
remotebranch.logbase = f'ou=ldapsync{id},{branch.dc}'
remotebranches.append(remotebranch)
branchdata = {'local' : log_server , 'remote' : remotebranches} def pullfromall(self, log_server: logserver)->int:
self.branches.append(branchdata) pulledlogs = 0
def remove(self, log_server: logbranch): for source in self.servers:
for branch in self.branches: if source != log_server:
if branch['local'] == log_server: pulledlogs += log_server.pullfrom(source)
self.branches.remove(branch)
def push(self): return pulledlogs
for branch in self.branches:
localtotal = branch.gettotal()
for remotebranch in branch['remote']:
remotetotal = remotebranch.gettotal()
for log in range(remotetotal + 1, localtotal + 1):
locallog = branch['local'].getlog(log)
remotebranch.logconnection.add(f'uid={log},{remotebranch.logbase}', OBJECTCLASSES, locallog['attributes'])
def sync(self): def pull(self)->int:
self.push() pulledlogs = 0
for branch in self.branches:
branch['local'].applylogs()
for localbranch in branch['remote']:
localbranch.applylogs()
for server in self.servers:
pulledlogs += self.pullfromall(server)
return pulledlogs
def applyfromall(self, log_server: logserver)->int:
appliedlogs = 0
for source in self.servers:
if source != log_server:
appliedlogs += log_server.applyfrom(source)
return appliedlogs
def apply(self)->int:
appliedlogs = 0
for server in self.servers:
appliedlogs += self.applyfromall(server)
return appliedlogs
def sync(self)->int:
pulledlogs = self.pull()
appliedlogs = self.apply()
return appliedlogs