From 80a64511cd40da355e5f4a413f067dfa7eeda53a Mon Sep 17 00:00:00 2001 From: fram3d Date: Fri, 5 Apr 2024 13:50:34 +0200 Subject: [PATCH] restrucute code --- models.py | 369 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 253 insertions(+), 116 deletions(-) diff --git a/models.py b/models.py index 88869be..a7767fb 100755 --- a/models.py +++ b/models.py @@ -1,36 +1,31 @@ import ldap3 import random +from datetime import datetime MAXENTRIES = 1000000 OBJECTCLASSES = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount'] 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): - self.ldap_host = ldap_host + +class logserver(): + 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_pass = admin_pass 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) -class logbranch(): - def __init__(self, ldap_server: ldapbind, log_server: ldapbind = None): + self.organization, self.dc, self.dcfull, self.domain = self.expandbase() - self.ldap_server = ldap_server - 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 + self.logbase = f'ou=log,{self.dc}' # Unique id of the log branch self.id = self.getid() @@ -41,145 +36,287 @@ class logbranch(): # How many changes are recoreded in total self.total = self.gettotal() - def getid(self)->int: - self.logconnection.search(search_base=f'uid=id,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) - response = self.logconnection.response + def expandbase(self, base: str = '')->(str,str,str,str): + ''' + 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 == []: 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 int(response[0]['attributes']['uidNumber']) + else: + response = int(response[0]['attributes']['uidNumber']) - def gettotal(self)->int: - self.logconnection.search(search_base=f'uid=total,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) - response = self.logconnection.response + return 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 == []: - settotal(0) - return 0 - return int(response[0]['attributes']['uidNumber']) + response = 0 + else: + 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) # Check if total record already present - self.logconnection.search(search_base=f'uid=total,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) - response = self.logconnection.response + self.conn.search(search_base=f'uid=total,{base}',search_filter='(objectClass=person)', attributes=['uidNumber']) + 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 == []: - 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.logconnection.response + return self.conn.response - def getloaded(self): - self.connection.search(search_base=f'uid=loaded,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) - response = self.logconnection.response + def getloaded(self, base: str = '')->int: + if base == '': + base = self.logbase + + self.conn.search(search_base=f'uid=loaded,{base}',search_filter='(objectClass=person)', attributes=['uidNumber']) + response = self.conn.response if response == []: - setloaded(0) - return 0 - return int(self.connection.response[0]['attributes']['uidNumber']) + response = 0 + else: + 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) # Check if loaded record already present - self.logconnection.search(search_base=f'uid=loaded,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) - response = self.logconnection.response + self.conn.search(search_base=f'uid=loaded,{base}',search_filter='(objectClass=person)', attributes=['uidNumber']) + 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 == []: - 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.logconnection.response + return self.conn.response - def refreshtotal(self): - self.settotal(self.findtotal()) - return self.gettotal() + def refreshtotal(self, base: str = '')->int: + if base == '': + base = self.logbase - def findtotal(self): - for entry in range(self.gettotal() + 1, MAXENTRIES): - self.logconnection.search(search_base=f'uid={entry},{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) - if self.logconnection.response == []: + self.settotal(self.findtotal(base)) + return self.gettotal(base) + + 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 - def applylogs(self): - self.refreshtotal() + def applylogs(self, base: str = '')->int: + if base == '': + base = self.logbase - loaded = self.getloaded() + 1 - total = self.gettotal() + 1 + self.refreshtotal(base) + + loaded = self.getloaded(base) + 1 + total = self.gettotal(base) + 1 for log_number in range(loaded, total): - self.setlog(log_number) + self.applylog(log_number, base) return total - loaded - def applylog(self, log_number: int): - log = self.getlog(log_number) - return self.setlog(self, log) + def applylog(self, log_number: int, base: str = ''): + if base == '': + base = self.logbase - def getlog(self, log_number: int): - self.logconnection.search(search_base=f'uid={log_number},{self.logbase}',search_filter = '(objectClass=person)', attributes = USERATTRIBUTES) - return logconnection.response[0] + log = self.getlog(log_number, base) - def setlog(self, log_number: int): - log = self.getlog(log_number) + attributes = log['attribtes'] + uid = attributes['uid'] + action = attributes['description'] + attributes['description'] = '' - attributes = log['attributes']['description'] - uid = attributes['uid'] - action = attributes['description'] - attributes['description'] = '' + if action == 'ADD': + self.conn.add(f'uid={uid},{base}', OBJECTCLASSES, attributes) + elif action == 'DELETE': + self.conn.delete(f'uid={uid},{base}') + elif action == 'CHANGEPASS': + self.conn.modify(f'uid={uid},{base}', {'userPassword': (MODIFY_REPLACE,attributes['userPassword'])}) + else: + return f'Error: Unrecognized action in log entry: {action}' - if action == 'ADD': - self.ldapconnection.add(f'uid={uid},{self.base}', OBJECTCLASSES, attributes) - elif action == 'DELETE': - self.ldapconnection.delete(f'uid={uid},{self.base}') - elif action == 'CHANGEPASS': - self.ldapconnection.modify(f'uid={uid},{self.base}', {'userPassword': (MODIFY_REPLACE,attributes['userPassword'])}) - else: - return f'Error: Unrecognized action in log entry: {action}' + self.setloaded(self.getloaded() + 1, base) + return self.conn.response - self.setloaded( self.getloaded() + 1 ) - return ldapconnection.response + def getlog(self, log_number: int, base: str = ''): + if base == '': + base = self.logbase -class logtree(): + 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): - self.branches = [] + self.servers = [] - def add(self, log_server: logbranch): - # Find ID of new branch - id = log_server.logconnection.search(search_base=f'{log_server.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) + def add(self, log_server: logserver)->list: + self.servers.append(log_server) + return self.servers - remotebranches = [] - for branch in self.branches: - # Create base for log copies of the new log branch to all existing ones - 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) + def remove(self, log_server: logserver)->list: + self.servers.remove(log_server) + return self.servers - branchdata = {'local' : log_server , 'remote' : remotebranches} - self.branches.append(branchdata) + def pullfromall(self, log_server: logserver)->int: + pulledlogs = 0 - def remove(self, log_server: logbranch): - for branch in self.branches: - if branch['local'] == log_server: - self.branches.remove(branch) + for source in self.servers: + if source != log_server: + pulledlogs += log_server.pullfrom(source) - def push(self): - 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']) + return pulledlogs - def sync(self): - self.push() - for branch in self.branches: - branch['local'].applylogs() - for localbranch in branch['remote']: - localbranch.applylogs() + def pull(self)->int: + pulledlogs = 0 + 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