Skip to content
Snippets Groups Projects
Commit bb9c49cc authored by Julian's avatar Julian
Browse files

Rewrote everything

parent 19a484ff
No related branches found
No related tags found
No related merge requests found
from enum import Enum
from copy import deepcopy from copy import deepcopy
class Status(Enum): class SessionState:
NEW def __init__(self, objects=None, deleted_objects=None):
ADDED self.objects = objects or {}
DELETED self.deleted_objects = deleted_objects or {}
class State: def copy(self):
def __init__(self, status=Status.NEW, attributes=None): return SessionState(objects=deepcopy(self.objects), deleted_objects=deepcopy(self.deleted_objects))
self.status = status
class ObjectState:
def __init__(self, session=None, attributes=None, dn=None):
self.session = session
self.attributes = attributes or {} self.attributes = attributes or {}
self.dn = dn
def copy(self): def copy(self):
return State(self.status, deepcopy(self.attributes)) return ObjectState(attributes=deepcopy(self.attributes), dn=self.dn, session=self.session)
class Operation: class AddOperation:
def __init__(self, obj): def __init__(self, obj, dn, object_classes):
self.obj = obj self.obj = obj
self.dn = dn
self.object_classes = object_classes
self.attributes = deepcopy(obj.state.attributes)
def apply(self, state): def apply_object(self, obj_state):
raise NotImplemented() obj_state.dn = self.dn
obj_state.attributes = deepcopy(self.attributes)
def execute(self, conn): def apply_session(self, session_state):
raise NotImplemented() assert self.dn not in session_state.objects
session_state.objects[self.dn] = self.obj
def extend(self, oper): def apply_ldap(self, conn):
return False success = conn.add(self.dn, self.object_classes, self.attributes)
class AddOperation(Operation):
def __init__(self, obj, attributes, ldap_object_classes):
super().__init__(obj)
self.attributes = deepcopy(attributes)
self.ldap_object_classes = ldap_object_classes
def apply(self, state):
state.status = Status.ADDED
state.attributes = self.attributes
def execute(self, conn):
success = conn.add(self.obj.dn, self.ldap_object_classes, self.attributes)
if not success: if not success:
raise LDAPCommitError() raise LDAPCommitError()
class DeleteOperation(Operation): class DeleteOperation:
def apply(self, state): def __init__(self, obj):
state.status = Status.DELETED self.dn = obj.state.dn
self.obj = obj
def apply_object(self, obj_state):
obj_state.dn = None
def execute(self, conn): def apply_session(self, session_state):
success = conn.delete(self.obj.dn) assert self.dn in session_state.objects
del session_state.objects[self.dn]
session_state.deleted_objects[self.dn] = self.obj
def apply_ldap(self, conn):
success = conn.delete(self.dn)
if not success: if not success:
raise LDAPCommitError() raise LDAPCommitError()
class ModifyOperation(Operation): class ModifyOperation:
def __init__(self, obj, changes): def __init__(self, obj, changes):
super().__init__(obj) self.obj = obj
self.changes = deepcopy(changes) self.changes = deepcopy(changes)
def apply(self, state): def apply_object(self, obj_state):
for attr, changes in self.changes.items(): for attr, changes in self.changes.items():
for action, values in changes: for action, values in changes:
if action == MODIFY_REPLACE: if action == MODIFY_REPLACE:
state.attributes[attr] = values obj_state.attributes[attr] = values
elif action == MODIFY_ADD: elif action == MODIFY_ADD:
state.attributes[attr] += values obj_state.attributes[attr] += values
elif action == MODIFY_DELETE: elif action == MODIFY_DELETE:
for value in values: for value in values:
state.attributes[attr].remove(value) obj_state.attributes[attr].remove(value)
def execute(self, conn): def apply_session(self, session_state):
success = conn.modify(self.obj.dn, self.changes) pass
def apply_ldap(self, conn):
success = conn.modify(self.obj.state.dn, self.changes)
if not success: if not success:
raise LDAPCommitError() raise LDAPCommitError()
class Session: class Session:
ldap_mapper = None def __init__(self, get_connection):
self.get_connection = get_connection
def __init__(self): self.committed_state = SessionState()
self.__objects = {} self.state = SessionState()
self.__deleted_objects = {} self.changes = []
self.__operations = []
def add(self, obj, dn, object_classes):
# Never called directly! if self.state.objects.get(dn) == obj:
def record(self, oper):
if isinstance(oper, AddOperation):
if oper.obj.ldap_state.session == self:
return return
if oper.obj.ldap_state.session is not None: assert obj.state.session is None
raise Exception() oper = AddOperation(obj, dn, object_classes)
if oper.obj.dn in self.__objects: oper.apply_object(obj.state)
raise Exception() oper.apply_session(self.state)
self.__objects[oper.obj.dn] = oper.obj self.changes.append(oper)
elif isinstance(oper, DeleteOperation):
if oper.obj.ldap_state.session is None:
return
if oper.obj.ldap_state.session != self:
raise Exception()
if oper.obj.dn not in self.__objects:
raise Exception()
if oper.obj.dn in self.__deleted_objects:
raise Exception()
self.__deleted_objects[oper.obj.dn] = oper.obj
del self.__objects[oper.obj.dn]
else:
if oper.obj.ldap_state.session is None:
return
if not self.__operations or not self.__operations[-1].extend(oper):
self.__operations.append(oper)
def add(self, obj):
obj.ldap_state.add(self)
def delete(self, obj): def delete(self, obj):
obj.ldap_state.delete() if obj.state.dn not in self.state.objects:
return
assert obj.state.session == self
oper = DeleteOperation(obj)
oper.apply_object(obj.state)
obj.state.session = None
oper.apply_session(self.state)
self.changes.append(oper)
def record(self, oper):
assert oper.obj.state.session == self
self.changes.append(oper)
def commit(self): def commit(self):
conn = self.mapper.connect() conn = self.get_connection()
while self.__operations: while self.changes:
obj, oper = self.__operations.pop(0) oper = self.changes.pop(0)
try: try:
oper.execute(obj.dn, conn) oper.apply_ldap(conn)
except e: except e:
self.__operations.insert(0, (obj, oper)) self.changes.insert(0, oper)
raise e raise e
oper.apply_object(oper.obj.committed_state)
oper.apply_session(self.committed_state)
self.committed_state = self.state.copy()
def rollback(self): def rollback(self):
while self.__operations: for obj in self.state.objects.values():
obj, oper = self.__operations.pop(0) obj.state = obj.committed_state.copy()
obj.ldap_state.current = obj.ldap_state.committed.copy() for obj in self.state.deleted_objects.values():
obj.state = obj.committed_state.copy()
def query_get(self, cls, dn): self.state = self.committed_state.copy()
if dn in self.__objects: self.changes.clear()
return self.__objects[dn]
if dn in self.__deleted_objects: def get(self, dn, search_filter):
if dn in self.state.objects:
return self.state.objects[dn]
if dn in self.state.deleted_objects:
return None return None
conn = self.mapper.connect() conn = self.get_connection()
conn.search(dn, cls.ldap_filter, attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES]) conn.search(dn, search_filter, attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES])
if not conn.response: if not conn.response:
return None return None
self.__objects[dn] = cls(__ldap_response=conn.response[0]) assert len(conn.response) == 1
return self.__objects[dn] assert conn.response[0]['dn'] == dn
self.state.objects[dn] = Object(self, conn.response[0])
def query_search(self, cls, filters=None): self.committed_state.objects[dn] = self.state.objects[dn]
filters = [cls.ldap_filter] + (filters or []) return self.state.objects[dn]
if len(filters) == 1:
expr = filters[0] def search(self, search_base, search_filter):
else: conn = self.get_connection()
expr = '(&%s)'%(''.join(filters)) conn.search(search_base, search_filter, attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES])
conn = self.mapper.connect()
conn.search(cls.ldap_base, cls.ldap_filter, attributes=[ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES])
res = [] res = []
for response in conn.response: for response in conn.response:
dn = response['dn'] dn = response['dn']
if dn in self.__objects: if dn in self.state.objects:
res.append(self.__objects[dn]) res.append(self.state.objects[dn])
elif dn in self.__deleted_objects: elif dn in self.state.deleted_objects:
continue continue
else: else:
self.__objects[dn] = cls(__ldap_response=response) self.state.objects[dn] = Object(self, response)
res.append(self.__objects[dn]) self.committed_state.objects[dn] = self.state.objects[dn]
res.append(self.state.objects[dn])
return res return res
# This is only a seperate class to keep SessionObject's namespace cleaner class Object:
class SessionObjectState: def __init__(self, session=None, response=None):
def __init__(self, obj, response=None): if response is None:
self.obj = obj self.committed_state = ObjectState()
self.session = None
if response is not None:
self.commited = State()
else: else:
self.commited = State(Status.ADDED, response['attributes']) assert session is not None
self.current = self.commited.copy() self.committed_state = ObjectState(session, response['attributes'], response['dn'])
self.state = self.committed_state.copy()
def add(self, session):
if self.session is not None:
return
# TODO: call hook functions
oper = AddOperation(self.current.attributes, self.obj.ldap_object_classes)
self.session.record(self.obj, oper)
self.session = session
oper.apply(self.current)
def delete(self):
if self.session is None:
return
oper = DeleteOperation()
self.session.record(self.obj, oper)
self.session = None
oper.apply(self.current)
def getattr(self, name): def getattr(self, name):
return self.current.attributes.get(name, []) return self.state.attributes.get(name, [])
def setattr(self, name, values): def setattr(self, name, values):
oper = ModifyOperation({name: [(MODIFY_REPLACE, [values])]}) oper = ModifyOperation(self, {name: [(MODIFY_REPLACE, [values])]})
if self.session is not None: oper.apply_object(obj.state)
self.session.record(self.obj, oper) if self.state.session:
oper.apply(self.current) oper.apply_session(self.state.session.state)
self.state.session.changes.append(oper)
def attr_append(self, name, value): def attr_append(self, name, value):
oper = ModifyOperation({name: [(MODIFY_ADD, [value])]}) oper = ModifyOperation(self, {name: [(MODIFY_ADD, [value])]})
if self.session is not None: oper.apply_object(obj.state)
self.session.record(self.obj, oper) if self.state.session:
oper.apply(self.current) oper.apply_session(self.state.session.state)
self.state.session.changes.append(oper)
def attr_remove(self, name, value): def attr_remove(self, name, value):
# TODO: how does LDAP handle MODIFY_DELETE ops with non-existant values? oper = ModifyOperation(self, {name: [(MODIFY_DELETE, [value])]})
oper = ModifyOperation({name: [(MODIFY_DELETE, [value])]}) oper.apply_object(obj.state)
if self.session is not None: if self.state.session:
self.session.record(self.obj, oper) oper.apply_session(self.state.session.state)
oper.apply(self.current) self.state.session.changes.append(oper)
# This is only a seperate class to keep SessionObject's namespace cleaner
class SessionObject:
ldap_mapper = None
ldap_object_classes = None
ldap_base = None
ldap_filter = None
def __init__(self, __ldap_response=None):
self.ldap_state = SessionObjectState(self, __ldap_response)
@property
def dn(self):
raise NotImplemented()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment