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