lgit/models.py

323 lines
11 KiB
Python
Raw Normal View History

2024-02-08 17:21:39 +00:00
import ldap3
2024-03-01 09:07:39 +00:00
import random
2024-04-05 11:50:34 +00:00
from datetime import datetime
2024-02-08 17:21:39 +00:00
MAXENTRIES = 1000000
OBJECTCLASSES = ['top', 'person', 'organizationalPerson', 'inetOrgPerson', 'posixAccount', 'shadowAccount']
USERATTRIBUTES = ['cn' , 'sn', 'givenName', 'uid', 'uidNumber' , 'gidNumber', 'homeDirectory', 'loginShell', 'gecos' , 'shadowLastChange', 'shadowMax', 'userPassword', 'mail', 'description']
2024-04-05 11:50:34 +00:00
class logserver():
def __init__(self, _host: str, admin_user: str, admin_pass: str, base: str, ssl: bool = True ):
self._host = ldap_host
2024-02-08 17:21:39 +00:00
self.admin_user = admin_user
2024-03-01 09:07:39 +00:00
self.admin_pass = admin_pass
2024-02-08 17:21:39 +00:00
self.base = base
2024-04-05 11:50:34 +00:00
self.ssl = ssl
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
if ssl:
server = ldap3.Server(self.ldap_host,use_ssl=True)
else:
server = ldap3.Server(self.ldap_host)
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
self.conn = ldap3.Connection(ldapserver, admin_user, admin_pass, auto_bind=True)
2024-03-01 09:07:39 +00:00
2024-04-05 11:50:34 +00:00
self.organization, self.dc, self.dcfull, self.domain = self.expandbase()
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
self.logbase = f'ou=log,{self.dc}'
2024-02-08 17:21:39 +00:00
2024-03-01 09:07:39 +00:00
# Unique id of the log branch
self.id = self.getid()
2024-02-08 17:21:39 +00:00
# How many changes we applied to the LDAP server
self.loaded = self.getloaded()
# How many changes are recoreded in total
self.total = self.gettotal()
2024-04-05 11:50:34 +00:00
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
2024-03-01 09:07:39 +00:00
if response == []:
id = random.randint(10**12, 10**13-1)
2024-04-05 11:50:34 +00:00
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)
2024-03-01 09:07:39 +00:00
return id
2024-04-05 11:50:34 +00:00
else:
response = int(response[0]['attributes']['uidNumber'])
return response
2024-03-01 09:07:39 +00:00
2024-04-05 11:50:34 +00:00
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
2024-02-08 17:21:39 +00:00
if response == []:
2024-04-05 11:50:34 +00:00
response = 0
else:
response = int(response[0]['attributes']['uidNumber'])
return response
def settotal(self, newvalue: int, base: str = ''):
if base == '':
base = self.logbase
2024-02-08 17:21:39 +00:00
newvalue = int(newvalue)
# Check if total record already present
2024-04-05 11:50:34 +00:00
self.conn.search(search_base=f'uid=total,{base}',search_filter='(objectClass=person)', attributes=['uidNumber'])
response = self.conn.response
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
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}' }
2024-02-08 17:21:39 +00:00
if response == []:
2024-04-05 11:50:34 +00:00
self.conn.add(f'uid=total,{base}', OBJECTCLASSES, attributes)
else:
self.conn.modify(f'uid=total,{base}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])})
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
return self.conn.response
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
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
2024-02-08 17:21:39 +00:00
if response == []:
2024-04-05 11:50:34 +00:00
response = 0
else:
response = int(response[0]['attributes']['uidNumber'])
return response
def setloaded(self, newvalue: int, base: str = ''):
if base == '':
base = self.logbase
2024-02-08 17:21:39 +00:00
newvalue = int(newvalue)
# Check if loaded record already present
2024-04-05 11:50:34 +00:00
self.conn.search(search_base=f'uid=loaded,{base}',search_filter='(objectClass=person)', attributes=['uidNumber'])
response = self.conn.response
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
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}' }
2024-02-08 17:21:39 +00:00
if response == []:
2024-04-05 11:50:34 +00:00
self.conn.add(f'uid=loaded,{base}', OBJECTCLASSES, attributes)
else:
self.conn.modify(f'uid=loaded,{base}', {'uidNumber' : (ldap3.MODIFY_REPLACE, [newvalue])})
return self.conn.response
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
def refreshtotal(self, base: str = '')->int:
if base == '':
base = self.logbase
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
self.settotal(self.findtotal(base))
return self.gettotal(base)
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
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 == []:
2024-02-08 17:21:39 +00:00
return entry - 1
2024-04-05 11:50:34 +00:00
def applylogs(self, base: str = '')->int:
if base == '':
base = self.logbase
self.refreshtotal(base)
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
loaded = self.getloaded(base) + 1
total = self.gettotal(base) + 1
2024-02-08 17:21:39 +00:00
for log_number in range(loaded, total):
2024-04-05 11:50:34 +00:00
self.applylog(log_number, base)
2024-02-08 17:21:39 +00:00
return total - loaded
2024-04-05 11:50:34 +00:00
def applylog(self, log_number: int, base: str = ''):
if base == '':
base = self.logbase
log = self.getlog(log_number, base)
attributes = log['attribtes']
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}'
self.setloaded(self.getloaded() + 1, base)
return self.conn.response
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
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
if log_number == -1:
log_number = self.gettotal(base) + 1
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
self.conn.add(f'uid={log_number},{base}', OBJECTCLASSES, attributes)
return self.conn.response
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
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()
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
basecopy = f'ou=sync-{remoteid},{localdcfull}'
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
# 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}'})
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
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():
2024-03-01 09:07:39 +00:00
def __init__(self):
2024-04-05 11:50:34 +00:00
self.servers = []
def add(self, log_server: logserver)->list:
self.servers.append(log_server)
return self.servers
def remove(self, log_server: logserver)->list:
self.servers.remove(log_server)
return self.servers
def pullfromall(self, log_server: logserver)->int:
pulledlogs = 0
for source in self.servers:
if source != log_server:
pulledlogs += log_server.pullfrom(source)
return pulledlogs
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()
2024-02-08 17:21:39 +00:00
2024-04-05 11:50:34 +00:00
return appliedlogs