Switch to mixins to build async_ldap model classes

This commit is contained in:
sfigato 2020-08-31 11:01:46 +02:00
parent 205c87dc49
commit 67c83975d1
Signed by: blallo
GPG Key ID: 0CBE577C9B72DC3F
2 changed files with 151 additions and 0 deletions

View File

@ -0,0 +1,136 @@
# -*- encoding: utf-8 -*-
from bonsai import LDAPEntry, LDAPModOp, NoSuchObjectError # type: ignore
from phi.exceptions import (
PhiAttributeMissing,
PhiEntryDoesNotExist,
PhiEntryExists,
PhiUnauthorized,
PhiUnexpectedRuntimeValue,
)
ATTR_TAG = "_ldap_"
ATTR_TAG_LEN = len(ATTR_TAG)
async def build_heritage(obj, child_class, attribute_id="uid"):
"""
Given the object and the child class, yields the
instances of the children.
"""
async for child in obj.get_children():
if attribute_id in child:
_name = child[attribute_id][0]
yield child_class(_name, obj.client)
class Singleton(object):
"""
to singletonize a class. The class is crafted to be used with the mixins
that implement the compatible __init__.
"""
def __new__(cls, client, *args):
if "name" in args:
name = f"{cls.__name__}-{args['name']}-{id(client)}"
else:
name = f"{cls.__name__}-{id(client)}"
if name not in cls._instances:
cls._instances[name] = object.__new__(cls)
return cls._instances[name]
class Entry(object):
"""
to interact with LDAP.
"""
def get_all_ldap_attributes(self):
return [attr for attr in dir(self) if attr.startswith(ATTR_TAG)]
def __repr__(self):
return f"<{self.__class__.__name__} {self.dn}>"
def __str__(self):
return f"<{self.__class__.__name__} {self.dn}>"
def __iter__(self):
for attr in self.get_all_ldap_attributes():
if not isinstance(getattr(type(self), attr), property) and callable(
getattr(type(self), attr)
):
yield attr[ATTR_TAG_LEN:], getattr(self, attr)()
else:
yield attr[ATTR_TAG_LEN:], getattr(self, attr)
def __dict__(self):
return dict(self)
async def _create_new(self):
entry = LDAPEntry(self.dn)
entry["objectClass"] = self.object_class
entry.update(self.get_all_ldap_attributes())
async with self.client.connect(is_async=True) as conn:
await conn.add(entry)
return entry
async def _get(self):
async with self.client.connect(is_async=True) as conn:
# This returns a list of dicts. It should always contain only one item:
# the one we are interested.
_res = await conn.search(self.dn, 0)
if len(_res) == 0:
return
elif len(_res) > 1:
raise PhiUnexpectedRuntimeValue(
"return value should be no more than one", res
)
return _res[0]
async def describe(self):
res = dict(await self._get())
res["dn"] = self.dn
res["name"] = self.name
return res
class OrganizationalUnit(object):
def __init__(self, client):
self.client = client
self.base_dn = client.base_dn
self.name = self.__class__.__name__
self.children = build_heritage(self, getattr(self, "child_class"))
self._entry = LDAPEntry(self.dn)
def __aiter__(self):
return self
async def __anext__(self):
try:
return await self.children.__anext__()
except StopAsyncIteration:
self.children = build_heritage(self, getattr(self, "child_class"))
raise
async def get_children(self):
async with self.client.connect(is_async=True) as conn:
for el in await conn.search(self.dn, 2):
yield el
@property
def dn(self):
return f"ou={self.ou_name},{self.base_dn}"
class Child(object):
object_class = ["inetOrgPerson", "organizationalPerson", "person", "top"]
def __init__(self, client, name):
self.client = client
self.base_dn = client.base_dn
self.name = name
self._entry = LDAPEntry(self.dn)
@property
def dn(self):
return f"{self.id_tag}={self.name},ou={self.ou_name},{self.base_dn}"

View File

@ -0,0 +1,15 @@
# -*- encoding: utf-8 -*-
from phi.async_ldap import mixins
class User(mixins.Singleton, mixins.Entry, mixins.Child):
_instances = dict()
id_tag = "uid"
ou_name = "Hackers"
class Hackers(mixins.Singleton, mixins.Entry, mixins.OrganizationalUnit):
_instances = dict()
ou_name = "Hackers"
child_class = User