# -*- encoding: utf-8 -*- from bonsai import LDAPEntry from multidict import MultiDict from phi.async_ldap import mixins def parse_dn(dn): return MultiDict(e.split("=") for e in dn.split(",")) def get_dn(obj): if isinstance(obj, mixins.Entry): return obj.dn elif isinstance(obj, LDAPEntry): return obj["dn"] elif isinstance(obj, str): return obj else: raise ValueError(f"Unacceptable input: {obj}") class User(mixins.Member, mixins.Entry, mixins.Singleton): object_class = [ "inetOrgPerson", "simpleSecurityObject", "organizationalPerson", "person", "top", ] _instances = dict() # type: ignore id_tag = "uid" ou = "Hackers" ldap_attributes = ["uid", "ou", "cn", "sn", "mail", "userPassword"] async def iter_groups(self): # To be monkeypatched later pass # pragma: no cover async def groups(self): return [g async for g in self.iter_groups()] async def delete(self): async for group in self.iter_groups(): await group.remove_member(self) await super().delete() class Hackers(mixins.OrganizationalUnit, mixins.Entry, mixins.Singleton): _instances = dict() # type: ignore ou = "Hackers" child_class = User class Service(mixins.Member, mixins.Entry, mixins.Singleton): object_class = ["simpleSecurityObject", "account", "top"] _instances = dict() # type: ignore id_tag = "uid" ou = "Robots" ldap_attributes = ["uid", "ou", "userPassword"] async def iter_groups(self): # To be monkeypatched later pass # pragma: no cover async def groups(self): return [g async for g in self.iter_groups()] async def delete(self): async for group in self.iter_groups(): await group.remove_member(self) await super().delete() class Robots(mixins.OrganizationalUnit, mixins.Entry, mixins.Singleton): _instances = dict() # type: ignore ou = "Robots" child_class = Service class Group(mixins.Member, mixins.Entry, mixins.Singleton): object_class = ["groupOfNames", "top"] _instances = dict() # type: ignore id_tag = "cn" ou = "Congregations" ldap_attributes = ["cn", "ou", "member"] memeber_classes = {"Hackers": User, "Robots": Service} empty = False async def add_member(self, member): member_dn = get_dn(member) self._entry["member"].append(member_dn) await self.modify() async def remove_member(self, member): new_group_members = [get_dn(m) async for m in self.get_members() if member != m] if len(new_group_members) == 0: await self.delete() self.empty = True else: self._entry["member"] = new_group_members await self.modify() async def get_members(self): await self.sync() for member in self._entry.get("member", []): dn = parse_dn(member) yield self.memeber_classes.get(dn["ou"])(self.client, dn["uid"]) class Congregations(mixins.OrganizationalUnit, mixins.Entry, mixins.Singleton): _instances = dict() # type: ignore ou = "Congregations" child_class = Group # We define this async method here **after** `User`, `Service` and `Group` have been # defined, in order to avoid definition loops that would prevent the code from running. # Indeed, this function explicitely uses `Group` but is needed as a `User` and `Service` # method. In turn, `Group` definition relies on both `User` and `Service` being yet # defined. async def iter_groups(self): async with self.client.connect(is_async=True) as conn: res = await conn.search(f"{self.dn}", 2, attrlist=["memberOf"]) if not res or len(res) == 0: return elif len(res) == 1: for group in res[0].get("memberOf", []): yield Group(self.client, parse_dn(get_dn(group))["cn"]) else: raise PhiUnexpectedRuntimeValue( "return value should be no more than one", res ) # Monkeypatch User.iter_groups = iter_groups # type: ignore Service.iter_groups = iter_groups # type: ignore