import ldap3 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 self.admin_user = admin_user self.admin_pass= admin_pass self.base = base 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): self.ldap_server = ldap_server self.log_server = log_server self.base = self.ldap_server.base self.logbase = self.log_server.base self.logconnection = self.log_server.conn self.ldapconnection = self.ldap_server.conn self.logconnection = self.log_server.conn # How many changes we applied to the LDAP server self.loaded = self.getloaded() # How many changes are recoreded in total self.total = self.gettotal() def gettotal(self)->int: self.logconnection.search(search_base=f'uid=total,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) response = self.logconnection.response if response == []: response = 0 return int(response['attributes']['uidNumber']) 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 if response == []: return self.logconnection.add(f'uid=total,{self.logbase}', OBJECTCLASSES, { 'uid' : 'total', 'uidNumber' : 0 }) return self.logconnection.modify(f'uid=total,{self.logbase}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])}) def getloaded(self): self.connection.search(search_base=f'uid=loaded,{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) response = self.logconnection.response if response == []: response = 0 return int(self.connection.response['attributes']['uidNumber']) 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 if response == []: return self.logconnection.add(f'uid=loaded,{self.logbase}', OBJECTCLASSES, { 'uid' : 'loaded', 'uidNumber' : 0 }) return self.logconnection.modify(f'uid=loaded,{self.logbase}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])}) def refreshtotal(self): self.settotal(self.findtotal()) return self.gettotal() def findtotal(self): for entry in range(self.gettotal(), MAXENTRIES): self.logconnection.search(search_base=f'uid={entry},{self.logbase}',search_filter='(objectClass=person)', attributes=['uidNumber']) if self.logconnection.response == []: return entry - 1 def applylogs(self): self.refreshtotal() loaded = int(self.getloaded()) + 1 total = int(self.gettotal()) + 1 for log_number in range(loaded, total): self.setlog(log_number) return total - loaded def applylog(self, log_number: int): log = self.getlog(log_number) return self.setlog(self, log) 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 def setlog(self, log_number: int): log = self.getlog(log_number) attributes = log['attributes']['description'] uid = attributes['uid'] action = attributes['description'] attributes['description'] = '' 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 ) return ldapconnection.response class logtree(): def __init__(self, ldap_server: ldapbind): self.ldap_server = ldap_server self.branches = [] def add(self, log_server: logbranch): self.branches.append(log_server) def remove(self, log_server: logbranch): self.branches.remove(log_server) def sync(self): for branch in self.branches: branch.applylogs()