phi/src/phi/async_ldap/model.py

141 lines
4.1 KiB
Python

# -*- encoding: utf-8 -*-
from bonsai import LDAPEntry
from multidict import MultiDict
from phi.async_ldap import mixins
from phi.exceptions import (
PhiUnexpectedRuntimeValue,
)
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", "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
raise NotImplemented
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 = "Roles"
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 Roles(mixins.OrganizationalUnit, mixins.Entry, mixins.Singleton):
_instances = dict() # type: ignore
ou = "Roles"
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