Massive code update
This commit is contained in:
parent
d024232d66
commit
ac0ab02d6e
|
@ -30,7 +30,6 @@ from phi.async_ldap.model import (
|
||||||
)
|
)
|
||||||
from phi.security import hash_pass
|
from phi.security import hash_pass
|
||||||
|
|
||||||
|
|
||||||
BASE_DN = "dc=test,dc=abbiamoundominio,dc=org"
|
BASE_DN = "dc=test,dc=abbiamoundominio,dc=org"
|
||||||
USER_LIST = [{"uid": ["conte_mascetti"]}, {"uid": ["perozzi"]}, {"uid": ["necchi"]}]
|
USER_LIST = [{"uid": ["conte_mascetti"]}, {"uid": ["perozzi"]}, {"uid": ["necchi"]}]
|
||||||
SERVICE_LIST = [{"uid": ["phi"]}, {"uid": ["irc"]}]
|
SERVICE_LIST = [{"uid": ["phi"]}, {"uid": ["irc"]}]
|
||||||
|
|
96
async_tests/test_async_ldap_new_model.py
Normal file
96
async_tests/test_async_ldap_new_model.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from async_generator import asynccontextmanager
|
||||||
|
from bonsai import LDAPEntry
|
||||||
|
import mock
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from phi.async_ldap.new_model import get_dn, User
|
||||||
|
|
||||||
|
BASE_DN = "dc=test,dc=domain,dc=tld"
|
||||||
|
|
||||||
|
|
||||||
|
class MockClient(object):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.return_value = kwargs.get("return_value")
|
||||||
|
self.connect_called = False
|
||||||
|
self.conn = mock.MagicMock()
|
||||||
|
|
||||||
|
def connect_called_with_search(self):
|
||||||
|
self.conn.search.assert_called()
|
||||||
|
|
||||||
|
def connect_called_with_add(self):
|
||||||
|
self.conn.add.assert_called()
|
||||||
|
|
||||||
|
def connect_called_with_modify(self):
|
||||||
|
self.conn.modify.assert_called()
|
||||||
|
|
||||||
|
def connect_called_with_delete(self):
|
||||||
|
self.conn.delete.assert_called()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base_dn(self):
|
||||||
|
return BASE_DN
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def connect(self, *args, **kwargs):
|
||||||
|
self.connect_called = True
|
||||||
|
|
||||||
|
async def _search(*a, **kw):
|
||||||
|
return self.return_value
|
||||||
|
|
||||||
|
async def _add(*a, **kw):
|
||||||
|
return self.return_value
|
||||||
|
|
||||||
|
async def _modify(*a, **kw):
|
||||||
|
return self.return_value
|
||||||
|
|
||||||
|
async def _delete(*a, **kw):
|
||||||
|
return self.return_value
|
||||||
|
|
||||||
|
self.conn.search = mock.MagicMock(side_effect=_search)
|
||||||
|
self.conn.add = mock.MagicMock(side_effect=_add)
|
||||||
|
self.conn.modify = mock.MagicMock(side_effect=_modify)
|
||||||
|
self.conn.delete = mock.MagicMock(side_effect=_delete)
|
||||||
|
|
||||||
|
yield self.conn
|
||||||
|
|
||||||
|
|
||||||
|
cl = mock.MagicMock()
|
||||||
|
cl.base_dn = BASE_DN
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"input_obj, expected_result",
|
||||||
|
[
|
||||||
|
(User(cl, "test_user"), f"uid=test_user,ou=Hackers,{BASE_DN}"),
|
||||||
|
(
|
||||||
|
LDAPEntry(f"uid=test_user,ou=Hackers,{BASE_DN}"),
|
||||||
|
f"uid=test_user,ou=Hackers,{BASE_DN}",
|
||||||
|
),
|
||||||
|
(f"uid=test_user,ou=Hackers,{BASE_DN}", f"uid=test_user,ou=Hackers,{BASE_DN}"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_get_dn(input_obj, expected_result):
|
||||||
|
assert get_dn(input_obj) == expected_result
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_dn_raises():
|
||||||
|
with pytest.raises(ValueError) as e:
|
||||||
|
_ = get_dn(object)
|
||||||
|
|
||||||
|
assert "Unacceptable input:" in str(e.value)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_User_add():
|
||||||
|
_cl = MockClient(return_value=None)
|
||||||
|
u = User(_cl, "test_user")
|
||||||
|
|
||||||
|
assert u.dn == f"uid=test_user,ou=Hackers,{BASE_DN}"
|
||||||
|
|
||||||
|
_ = await u.save()
|
||||||
|
|
||||||
|
assert _cl.connect_called
|
||||||
|
_cl.connect_called_with_add()
|
|
@ -11,31 +11,31 @@ objectClass: organizationalUnit
|
||||||
objectClass: top
|
objectClass: top
|
||||||
ou: Hackers
|
ou: Hackers
|
||||||
|
|
||||||
dn: ou=Services,dc=unit,dc=macaomilano,dc=org
|
dn: ou=Robots,dc=unit,dc=macaomilano,dc=org
|
||||||
objectClass: top
|
objectClass: top
|
||||||
objectClass: organizationalUnit
|
objectClass: organizationalUnit
|
||||||
ou: Services
|
ou: Robots
|
||||||
|
|
||||||
dn: ou=Groups,dc=unit,dc=macaomilano,dc=org
|
dn: ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
objectClass: top
|
objectClass: top
|
||||||
objectClass: organizationalUnit
|
objectClass: organizationalUnit
|
||||||
ou: Groups
|
ou: Congregations
|
||||||
|
|
||||||
dn: uid=phi,ou=Services,dc=unit,dc=macaomilano,dc=org
|
dn: uid=phi,ou=Robots,dc=unit,dc=macaomilano,dc=org
|
||||||
objectClass: account
|
objectClass: account
|
||||||
objectClass: simpleSecurityObject
|
objectClass: simpleSecurityObject
|
||||||
objectClass: top
|
objectClass: top
|
||||||
uid: phi
|
uid: phi
|
||||||
userPassword: {SHA}REu9CtcqSaA1c5J+sEYlTgg0H+M=
|
userPassword: {SHA}REu9CtcqSaA1c5J+sEYlTgg0H+M=
|
||||||
|
|
||||||
dn: uid=irc,ou=Services,dc=unit,dc=macaomilano,dc=org
|
dn: uid=irc,ou=Robots,dc=unit,dc=macaomilano,dc=org
|
||||||
objectClass: account
|
objectClass: account
|
||||||
objectClass: simpleSecurityObject
|
objectClass: simpleSecurityObject
|
||||||
objectClass: top
|
objectClass: top
|
||||||
uid: irc
|
uid: irc
|
||||||
userPassword: {SHA}0WvxFW9MSsesf55SOh4vnuwdkgY=
|
userPassword: {SHA}0WvxFW9MSsesf55SOh4vnuwdkgY=
|
||||||
|
|
||||||
dn: uid=git,ou=Services,dc=unit,dc=macaomilano,dc=org
|
dn: uid=git,ou=Robots,dc=unit,dc=macaomilano,dc=org
|
||||||
objectClass: account
|
objectClass: account
|
||||||
objectClass: simpleSecurityObject
|
objectClass: simpleSecurityObject
|
||||||
objectClass: top
|
objectClass: top
|
||||||
|
@ -47,9 +47,9 @@ objectClass: inetOrgPerson
|
||||||
objectClass: organizationalPerson
|
objectClass: organizationalPerson
|
||||||
objectClass: person
|
objectClass: person
|
||||||
objectClass: top
|
objectClass: top
|
||||||
memberOf: cn=Admins,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
memberOf: cn=Admins,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
memberOf: cn=GitUsers,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
memberOf: cn=GitUsers,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
memberOf: cn=IRCUsers,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
memberOf: cn=IRCUsers,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
cn: Raffaello
|
cn: Raffaello
|
||||||
sn: Mascetti
|
sn: Mascetti
|
||||||
mail: rmascetti@autistici.org
|
mail: rmascetti@autistici.org
|
||||||
|
@ -61,8 +61,8 @@ objectClass: inetOrgPerson
|
||||||
objectClass: organizationalPerson
|
objectClass: organizationalPerson
|
||||||
objectClass: person
|
objectClass: person
|
||||||
objectClass: top
|
objectClass: top
|
||||||
memberOf: cn=GitUsers,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
memberOf: cn=GitUsers,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
memberOf: cn=IRCUsers,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
memberOf: cn=IRCUsers,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
cn: Guido
|
cn: Guido
|
||||||
sn: Necchi
|
sn: Necchi
|
||||||
mail: gnecchi@autistici.org
|
mail: gnecchi@autistici.org
|
||||||
|
@ -74,20 +74,20 @@ objectClass: inetOrgPerson
|
||||||
objectClass: organizationalPerson
|
objectClass: organizationalPerson
|
||||||
objectClass: person
|
objectClass: person
|
||||||
objectClass: top
|
objectClass: top
|
||||||
memberOf: cn=GitUsers,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
memberOf: cn=GitUsers,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
cn: Giorgio
|
cn: Giorgio
|
||||||
sn: Perozzi
|
sn: Perozzi
|
||||||
mail: gperozzi@autistici.org
|
mail: gperozzi@autistici.org
|
||||||
uid: perozzi
|
uid: perozzi
|
||||||
userPassword: {SHA}0+CRQKqsTj1I82PHxvZ4ebbddXQ=
|
userPassword: {SHA}0+CRQKqsTj1I82PHxvZ4ebbddXQ=
|
||||||
|
|
||||||
dn: cn=Admins,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
dn: cn=Admins,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
member: uid=conte_mascetti,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
member: uid=conte_mascetti,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
||||||
cn: Admins
|
cn: Admins
|
||||||
objectClass: groupOfNames
|
objectClass: groupOfNames
|
||||||
objectClass: top
|
objectClass: top
|
||||||
|
|
||||||
dn: cn=GitUsers,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
dn: cn=GitUsers,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
member: uid=conte_mascetti,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
member: uid=conte_mascetti,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
||||||
member: uid=necchi,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
member: uid=necchi,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
||||||
member: uid=perozzi,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
member: uid=perozzi,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
||||||
|
@ -95,7 +95,7 @@ cn: GitUsers
|
||||||
objectClass: groupOfNames
|
objectClass: groupOfNames
|
||||||
objectClass: top
|
objectClass: top
|
||||||
|
|
||||||
dn: cn=IRCUsers,ou=Groups,dc=unit,dc=macaomilano,dc=org
|
dn: cn=IRCUsers,ou=Congregations,dc=unit,dc=macaomilano,dc=org
|
||||||
member: uid=conte_mascetti,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
member: uid=conte_mascetti,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
||||||
member: uid=necchi,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
member: uid=necchi,ou=Hackers,dc=unit,dc=macaomilano,dc=org
|
||||||
cn: IRCUsers
|
cn: IRCUsers
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
from bonsai import LDAPEntry, LDAPModOp, NoSuchObjectError # type: ignore
|
from bonsai import LDAPEntry, LDAPModOp, NoSuchObjectError # type: ignore
|
||||||
|
from bonsai.ldapvaluelist import LDAPValueList
|
||||||
|
import bonsai.errors
|
||||||
|
|
||||||
from phi.exceptions import (
|
from phi.exceptions import (
|
||||||
PhiAttributeMissing,
|
PhiAttributeMissing,
|
||||||
|
@ -7,10 +9,10 @@ from phi.exceptions import (
|
||||||
PhiEntryExists,
|
PhiEntryExists,
|
||||||
PhiUnauthorized,
|
PhiUnauthorized,
|
||||||
PhiUnexpectedRuntimeValue,
|
PhiUnexpectedRuntimeValue,
|
||||||
|
PhiCannotExecute,
|
||||||
)
|
)
|
||||||
|
|
||||||
ATTR_TAG = "_ldap_"
|
from phi.security import hash_pass, handle_password
|
||||||
ATTR_TAG_LEN = len(ATTR_TAG)
|
|
||||||
|
|
||||||
|
|
||||||
async def build_heritage(obj, child_class, attribute_id="uid"):
|
async def build_heritage(obj, child_class, attribute_id="uid"):
|
||||||
|
@ -21,18 +23,20 @@ async def build_heritage(obj, child_class, attribute_id="uid"):
|
||||||
async for child in obj.get_children():
|
async for child in obj.get_children():
|
||||||
if attribute_id in child:
|
if attribute_id in child:
|
||||||
_name = child[attribute_id][0]
|
_name = child[attribute_id][0]
|
||||||
yield child_class(_name, obj.client)
|
yield child_class(obj.client, _name)
|
||||||
|
|
||||||
|
|
||||||
class Singleton(object):
|
class Singleton(object):
|
||||||
"""
|
"""
|
||||||
to singletonize a class. The class is crafted to be used with the mixins
|
Mixin to singletonize a class. The class is crafted to be used with the mixins
|
||||||
that implement the compatible __init__.
|
that implement the compatible __init__.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(cls, client, *args):
|
def __new__(cls, client, *args, **kwargs):
|
||||||
if "name" in args:
|
if "name" in kwargs:
|
||||||
name = f"{cls.__name__}-{args['name']}-{id(client)}"
|
name = f"{cls.__name__}-{args['name']}-{id(client)}"
|
||||||
|
elif args:
|
||||||
|
name = f"{cls.__name__}-{args[0]}-{id(client)}"
|
||||||
else:
|
else:
|
||||||
name = f"{cls.__name__}-{id(client)}"
|
name = f"{cls.__name__}-{id(client)}"
|
||||||
if name not in cls._instances:
|
if name not in cls._instances:
|
||||||
|
@ -40,13 +44,26 @@ class Singleton(object):
|
||||||
return cls._instances[name]
|
return cls._instances[name]
|
||||||
|
|
||||||
|
|
||||||
|
def get_value(obj, attr):
|
||||||
|
"""
|
||||||
|
Return the tuple (attribute_name, attribute_value) from obj. Extract the value,
|
||||||
|
either it being a constant or the result of a function call.
|
||||||
|
"""
|
||||||
|
if not isinstance(getattr(type(obj), attr), property) and callable(
|
||||||
|
getattr(type(obj), attr)
|
||||||
|
):
|
||||||
|
return attr, getattr(obj, attr)()
|
||||||
|
else:
|
||||||
|
return attr, getattr(obj, attr)
|
||||||
|
|
||||||
|
|
||||||
class Entry(object):
|
class Entry(object):
|
||||||
"""
|
"""
|
||||||
to interact with LDAP.
|
Mixin to interact with LDAP.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_all_ldap_attributes(self):
|
def get_all_ldap_attributes(self):
|
||||||
return [attr for attr in dir(self) if attr.startswith(ATTR_TAG)]
|
return [get_value(self, attr) for attr in self.ldap_attributes]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<{self.__class__.__name__} {self.dn}>"
|
return f"<{self.__class__.__name__} {self.dn}>"
|
||||||
|
@ -55,24 +72,17 @@ class Entry(object):
|
||||||
return f"<{self.__class__.__name__} {self.dn}>"
|
return f"<{self.__class__.__name__} {self.dn}>"
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for attr in self.get_all_ldap_attributes():
|
for k, v in self.get_all_ldap_attributes():
|
||||||
if not isinstance(getattr(type(self), attr), property) and callable(
|
yield k, v
|
||||||
getattr(type(self), attr)
|
|
||||||
):
|
|
||||||
yield attr[ATTR_TAG_LEN:], getattr(self, attr)()
|
|
||||||
else:
|
|
||||||
yield attr[ATTR_TAG_LEN:], getattr(self, attr)
|
|
||||||
|
|
||||||
def __dict__(self):
|
def __dict__(self):
|
||||||
return dict(self)
|
return dict(self)
|
||||||
|
|
||||||
async def _create_new(self):
|
async def _create_new(self):
|
||||||
entry = LDAPEntry(self.dn)
|
self._entry["objectClass"] = self.object_class
|
||||||
entry["objectClass"] = self.object_class
|
|
||||||
entry.update(self.get_all_ldap_attributes())
|
|
||||||
async with self.client.connect(is_async=True) as conn:
|
async with self.client.connect(is_async=True) as conn:
|
||||||
await conn.add(entry)
|
await conn.add(self._entry)
|
||||||
return entry
|
return self._entry
|
||||||
|
|
||||||
async def _get(self):
|
async def _get(self):
|
||||||
async with self.client.connect(is_async=True) as conn:
|
async with self.client.connect(is_async=True) as conn:
|
||||||
|
@ -80,27 +90,50 @@ class Entry(object):
|
||||||
# the one we are interested.
|
# the one we are interested.
|
||||||
_res = await conn.search(self.dn, 0)
|
_res = await conn.search(self.dn, 0)
|
||||||
if len(_res) == 0:
|
if len(_res) == 0:
|
||||||
return
|
raise PhiEntryDoesNotExist()
|
||||||
elif len(_res) > 1:
|
elif len(_res) > 1:
|
||||||
raise PhiUnexpectedRuntimeValue(
|
raise PhiUnexpectedRuntimeValue(
|
||||||
"return value should be no more than one", res
|
"return value should be no more than one", res
|
||||||
)
|
)
|
||||||
return _res[0]
|
return _res[0]
|
||||||
|
|
||||||
|
async def _modify(self):
|
||||||
|
_ = await self._get()
|
||||||
|
async with self.client.connect(is_async=True) as conn:
|
||||||
|
self._entry.connection = conn
|
||||||
|
await self._entry.modify()
|
||||||
|
|
||||||
|
async def _delete(self):
|
||||||
|
async with self.client.connect(is_async=True) as conn:
|
||||||
|
await conn.delete(self.dn)
|
||||||
|
|
||||||
async def describe(self):
|
async def describe(self):
|
||||||
res = dict(await self._get())
|
return dict(await self._get())
|
||||||
res["dn"] = self.dn
|
|
||||||
res["name"] = self.name
|
@property
|
||||||
return res
|
def delete_cascade(self):
|
||||||
|
if hasattr(self, "_delete_cascade"):
|
||||||
|
return self._delete_cascade
|
||||||
|
return False
|
||||||
|
|
||||||
|
@delete_cascade.setter
|
||||||
|
def delete_cascade(self, value):
|
||||||
|
if not isinstance(value, bool):
|
||||||
|
raise ValueError("delete_cascade must be a boolean")
|
||||||
|
self._delete_cascade = value
|
||||||
|
|
||||||
|
|
||||||
class OrganizationalUnit(object):
|
class OrganizationalUnit(object):
|
||||||
def __init__(self, client):
|
object_class = ["organizationalUnit", "top"]
|
||||||
|
|
||||||
|
def __init__(self, client, **kwargs):
|
||||||
self.client = client
|
self.client = client
|
||||||
self.base_dn = client.base_dn
|
self.base_dn = client.base_dn
|
||||||
self.name = self.__class__.__name__
|
self.name = self.__class__.__name__
|
||||||
self.children = build_heritage(self, getattr(self, "child_class"))
|
self.children = build_heritage(self, self.child_class, self.child_class.id_tag)
|
||||||
self._entry = LDAPEntry(self.dn)
|
self._entry = LDAPEntry(self.dn)
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
self._entry[k] = v
|
||||||
|
|
||||||
def __aiter__(self):
|
def __aiter__(self):
|
||||||
return self
|
return self
|
||||||
|
@ -109,28 +142,117 @@ class OrganizationalUnit(object):
|
||||||
try:
|
try:
|
||||||
return await self.children.__anext__()
|
return await self.children.__anext__()
|
||||||
except StopAsyncIteration:
|
except StopAsyncIteration:
|
||||||
self.children = build_heritage(self, getattr(self, "child_class"))
|
self.children = build_heritage(
|
||||||
|
self, self.child_class, self.child_class.id_tag
|
||||||
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def get_children(self):
|
async def get_children(self):
|
||||||
async with self.client.connect(is_async=True) as conn:
|
async with self.client.connect(is_async=True) as conn:
|
||||||
for el in await conn.search(self.dn, 2):
|
for el in await conn.search(self.dn, 1):
|
||||||
yield el
|
yield el
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dn(self):
|
def dn(self):
|
||||||
return f"ou={self.ou_name},{self.base_dn}"
|
return f"ou={self.ou},{self.base_dn}"
|
||||||
|
|
||||||
|
async def save(self):
|
||||||
|
try:
|
||||||
|
await self._create_new()
|
||||||
|
except bonsai.errors.AlreadyExists:
|
||||||
|
raise PhiEntryExists(self.dn)
|
||||||
|
|
||||||
|
async def search(self, member_name):
|
||||||
|
result = None
|
||||||
|
async with self.client.connect(is_async=True) as conn:
|
||||||
|
result = await conn.search(
|
||||||
|
f"{self.child_class.id_tag}={member_name},{self.dn}", 0
|
||||||
|
)
|
||||||
|
if not result:
|
||||||
|
raise PhiEntryDoesNotExist(
|
||||||
|
f"{self.child_class.id_tag}={member_name},{self.dn}"
|
||||||
|
)
|
||||||
|
if isinstance(result, list) and len(result) > 1:
|
||||||
|
raise PhiUnexpectedRuntimeValue(
|
||||||
|
"return value should be no more than one", result
|
||||||
|
)
|
||||||
|
return self.child_class(self.client, member_name, **result[0])
|
||||||
|
|
||||||
|
async def delete(self):
|
||||||
|
"""
|
||||||
|
Delete all the members of this OU only if `delete_cascade` is set to `True`,
|
||||||
|
raises otherwise.
|
||||||
|
"""
|
||||||
|
if self.delete_cascade:
|
||||||
|
async for member in self:
|
||||||
|
await member.delete()
|
||||||
|
else:
|
||||||
|
raise PhiCannotExecute("Cannot delete an OU and delete_cascade is not set")
|
||||||
|
|
||||||
|
|
||||||
class Child(object):
|
def hydrate(obj, data):
|
||||||
object_class = ["inetOrgPerson", "organizationalPerson", "person", "top"]
|
for k, v in data.items():
|
||||||
|
if k in obj.ldap_attributes:
|
||||||
|
if isinstance(v, list) and not isinstance(v, LDAPValueList):
|
||||||
|
obj._entry[k] = []
|
||||||
|
for _v in v:
|
||||||
|
obj._entry[k].append(_v.dn)
|
||||||
|
elif k == "userPassword":
|
||||||
|
obj._entry[k] = handle_password(v)
|
||||||
|
else:
|
||||||
|
obj._entry[k] = v
|
||||||
|
|
||||||
def __init__(self, client, name):
|
|
||||||
|
class Member(object):
|
||||||
|
def __init__(self, client, name, **kwargs):
|
||||||
self.client = client
|
self.client = client
|
||||||
self.base_dn = client.base_dn
|
self.base_dn = client.base_dn
|
||||||
self.name = name
|
self.name = name
|
||||||
self._entry = LDAPEntry(self.dn)
|
self._entry = LDAPEntry(self.dn)
|
||||||
|
if kwargs:
|
||||||
|
hydrate(self, kwargs)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, type(self)):
|
||||||
|
return other.dn == self.dn
|
||||||
|
elif isinstance(other, str):
|
||||||
|
return other == self.dn
|
||||||
|
elif isinstance(other, LDAPEntry):
|
||||||
|
return str(other) == self.dn
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dn(self):
|
def dn(self):
|
||||||
return f"{self.id_tag}={self.name},ou={self.ou_name},{self.base_dn}"
|
return f"{self.id_tag}={self.name},ou={self.ou},{self.base_dn}"
|
||||||
|
|
||||||
|
def __setitem__(self, attr, val):
|
||||||
|
if attr not in self.ldap_attributes:
|
||||||
|
raise PhiCannotExecute(
|
||||||
|
f"{attr} is not an allowed ldap_attribute: {self.ldap_attributes}"
|
||||||
|
)
|
||||||
|
self._entry[attr] = val
|
||||||
|
|
||||||
|
def __getitem__(self, attr):
|
||||||
|
if attr not in self.ldap_attributes:
|
||||||
|
raise PhiCannotExecute(
|
||||||
|
f"{attr} is not an allowed ldap_attribute: {self.ldap_attributes}"
|
||||||
|
)
|
||||||
|
return self._entry[attr][0]
|
||||||
|
|
||||||
|
async def save(self):
|
||||||
|
try:
|
||||||
|
await self._create_new()
|
||||||
|
except bonsai.errors.AlreadyExists:
|
||||||
|
raise PhiEntryExists(self.dn)
|
||||||
|
|
||||||
|
async def modify(self):
|
||||||
|
await self._modify()
|
||||||
|
|
||||||
|
async def delete(self):
|
||||||
|
await self._delete()
|
||||||
|
|
||||||
|
async def sync(self):
|
||||||
|
res = await self._get()
|
||||||
|
hydrate(self, res)
|
||||||
|
return self
|
||||||
|
|
|
@ -1,15 +1,79 @@
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
from bonsai import LDAPEntry
|
||||||
|
from multidict import MultiDict
|
||||||
|
|
||||||
from phi.async_ldap import mixins
|
from phi.async_ldap import mixins
|
||||||
|
|
||||||
|
|
||||||
class User(mixins.Singleton, mixins.Entry, mixins.Child):
|
class User(mixins.Singleton, mixins.Entry, mixins.Member):
|
||||||
_instances = dict()
|
object_class = [
|
||||||
|
"inetOrgPerson",
|
||||||
|
"simpleSecurityObject",
|
||||||
|
"organizationalPerson",
|
||||||
|
"person",
|
||||||
|
"top",
|
||||||
|
]
|
||||||
|
_instances = dict() # type: ignore
|
||||||
id_tag = "uid"
|
id_tag = "uid"
|
||||||
ou_name = "Hackers"
|
ou = "Hackers"
|
||||||
|
ldap_attributes = ["uid", "ou", "cn", "sn", "mail", "userPassword"]
|
||||||
|
|
||||||
|
|
||||||
class Hackers(mixins.Singleton, mixins.Entry, mixins.OrganizationalUnit):
|
class Hackers(mixins.Singleton, mixins.Entry, mixins.OrganizationalUnit):
|
||||||
_instances = dict()
|
_instances = dict() # type: ignore
|
||||||
ou_name = "Hackers"
|
ou = "Hackers"
|
||||||
child_class = User
|
child_class = User
|
||||||
|
|
||||||
|
|
||||||
|
class Service(mixins.Singleton, mixins.Entry, mixins.Member):
|
||||||
|
object_class = ["simpleSecurityObject", "account", "top"]
|
||||||
|
_instances = dict() # type: ignore
|
||||||
|
id_tag = "uid"
|
||||||
|
ou = "Robots"
|
||||||
|
ldap_attributes = ["uid", "ou", "userPassword"]
|
||||||
|
|
||||||
|
|
||||||
|
class Robots(mixins.Singleton, mixins.Entry, mixins.OrganizationalUnit):
|
||||||
|
_instances = dict() # type: ignore
|
||||||
|
ou = "Robots"
|
||||||
|
child_class = Service
|
||||||
|
|
||||||
|
|
||||||
|
class Group(mixins.Singleton, mixins.Entry, mixins.Member):
|
||||||
|
object_class = ["groupOfNames", "top"]
|
||||||
|
_instances = dict() # type: ignore
|
||||||
|
id_tag = "cn"
|
||||||
|
ou = "Congregations"
|
||||||
|
ldap_attributes = ["uid", "ou", "member"]
|
||||||
|
memeber_classes = {"Hackers": User, "Robots": Service}
|
||||||
|
|
||||||
|
async def add_member(self, member):
|
||||||
|
self._entry["member"].append(get_dn(member))
|
||||||
|
await self.modify()
|
||||||
|
|
||||||
|
async def remove_member(self, member):
|
||||||
|
self._entry["member"] = [get_dn(m) for m in self.get_members() if member != m]
|
||||||
|
await self.modify()
|
||||||
|
|
||||||
|
def get_members(self):
|
||||||
|
for member in self._entry.get("member", []):
|
||||||
|
dn = MultiDict(e.split("=") for e in member.split(","))
|
||||||
|
yield self.memeber_classes.get(dn["ou"])(self.client, dn["uid"])
|
||||||
|
|
||||||
|
|
||||||
|
class Congregations(mixins.Singleton, mixins.Entry, mixins.OrganizationalUnit):
|
||||||
|
_instances = dict() # type: ignore
|
||||||
|
ou = "Congregations"
|
||||||
|
child_class = Group
|
||||||
|
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
|
@ -5,11 +5,23 @@ class PhiEntryExists(Exception):
|
||||||
def __init__(self, dn):
|
def __init__(self, dn):
|
||||||
self.dn = dn
|
self.dn = dn
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Entry exists yet ({self.dn})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"PhiEntryExists({self.dn})"
|
||||||
|
|
||||||
|
|
||||||
class PhiEntryDoesNotExist(Exception):
|
class PhiEntryDoesNotExist(Exception):
|
||||||
def __init__(self, dn):
|
def __init__(self, dn):
|
||||||
self.dn = dn
|
self.dn = dn
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Entry does not exist ({self.dn})"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"PhiEntryDoesNotExist({self.dn})"
|
||||||
|
|
||||||
|
|
||||||
class PhiAttributeMissing(Exception):
|
class PhiAttributeMissing(Exception):
|
||||||
def __init__(self, dn, attr):
|
def __init__(self, dn, attr):
|
||||||
|
@ -28,3 +40,15 @@ class PhiUnexpectedRuntimeValue(RuntimeWarning):
|
||||||
super().__init__(self)
|
super().__init__(self)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.result = result
|
self.result = result
|
||||||
|
|
||||||
|
|
||||||
|
class PhiCannotExecute(RuntimeWarning):
|
||||||
|
def __init__(self, msg):
|
||||||
|
super().__init__(self)
|
||||||
|
self.msg = msg
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Cannot execute: {self.msg}"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"PhiCannotExecute({self.msg})"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
from bonsai.ldapvaluelist import LDAPValueList
|
||||||
from passlib.hash import (
|
from passlib.hash import (
|
||||||
ldap_sha1,
|
ldap_sha1,
|
||||||
ldap_bcrypt,
|
ldap_bcrypt,
|
||||||
|
@ -8,7 +9,6 @@ from passlib.hash import (
|
||||||
ldap_pbkdf2_sha512,
|
ldap_pbkdf2_sha512,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
HASH_CALLABLE = {
|
HASH_CALLABLE = {
|
||||||
"sha1": ldap_sha1.hash,
|
"sha1": ldap_sha1.hash,
|
||||||
"bcrypt": ldap_bcrypt.hash,
|
"bcrypt": ldap_bcrypt.hash,
|
||||||
|
@ -21,3 +21,9 @@ HASH_CALLABLE = {
|
||||||
|
|
||||||
def hash_pass(password, method="sha1"):
|
def hash_pass(password, method="sha1"):
|
||||||
return HASH_CALLABLE[method](password)
|
return HASH_CALLABLE[method](password)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_password(password, method="sha1"):
|
||||||
|
if isinstance(password, LDAPValueList):
|
||||||
|
return password
|
||||||
|
return hash_pass(password, method)
|
||||||
|
|
|
@ -3,19 +3,17 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from pprint import pprint as pp
|
from pprint import pprint as pp
|
||||||
|
|
||||||
from phi.async_ldap.model import (
|
from phi.async_ldap.new_model import (
|
||||||
# Hackers,
|
Hackers,
|
||||||
# User,
|
User,
|
||||||
Robots,
|
Robots,
|
||||||
Service,
|
Service,
|
||||||
Congregations,
|
|
||||||
Group,
|
Group,
|
||||||
build_heritage,
|
Congregations,
|
||||||
iter_children,
|
|
||||||
)
|
)
|
||||||
|
from phi.async_ldap.mixins import build_heritage
|
||||||
from phi.async_ldap.new_model import Hackers, User
|
|
||||||
from phi.async_ldap.client import AsyncClient
|
from phi.async_ldap.client import AsyncClient
|
||||||
|
import phi.exceptions as e
|
||||||
|
|
||||||
|
|
||||||
async def dlv(h, cls):
|
async def dlv(h, cls):
|
||||||
|
@ -47,15 +45,115 @@ async def get_all_children():
|
||||||
return (hackers, robots, groups)
|
return (hackers, robots, groups)
|
||||||
|
|
||||||
|
|
||||||
async def print_async(awaitable):
|
async def get_members(group):
|
||||||
|
return [el async for el in group]
|
||||||
|
|
||||||
|
|
||||||
|
async def print_async(label, awaitable):
|
||||||
|
print(label)
|
||||||
result = await awaitable
|
result = await awaitable
|
||||||
pp(result)
|
pp(result)
|
||||||
|
|
||||||
|
|
||||||
async def describe(obj):
|
async def describe(obj):
|
||||||
results = await obj.describe()
|
return await obj.describe()
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
asyncio.run(print_async(describe(Hackers(cl))))
|
async def _await(awaitable):
|
||||||
asyncio.run(print_async(describe(User(cl, "conte_mascetti"))))
|
return await awaitable
|
||||||
|
|
||||||
|
|
||||||
|
def sync_await(awaitable):
|
||||||
|
return asyncio.run(_await(awaitable))
|
||||||
|
|
||||||
|
|
||||||
|
h = Hackers(cl)
|
||||||
|
r = Robots(cl)
|
||||||
|
c = Congregations(cl)
|
||||||
|
|
||||||
|
|
||||||
|
# asyncio.run(print_async("hackers:", describe(h)))
|
||||||
|
# asyncio.run(print_async("conte_mascetti:", describe(User(cl, "conte_mascetti"))))
|
||||||
|
# asyncio.run(print_async("robots:", describe(r)))
|
||||||
|
# asyncio.run(print_async("phi:", describe(Service(cl, "phi"))))
|
||||||
|
# asyncio.run(print_async("congregations:", describe(c)))
|
||||||
|
# asyncio.run(print_async("GitUsers:", describe(Group(cl, "GitUsers"))))
|
||||||
|
|
||||||
|
# asyncio.run(print_async("Hackers members:", get_members(h)))
|
||||||
|
# asyncio.run(print_async("Robots members:", get_members(r)))
|
||||||
|
# asyncio.run(print_async("Congregations members:", get_members(c)))
|
||||||
|
|
||||||
|
#
|
||||||
|
async def add_new(obj, name, **kw):
|
||||||
|
try:
|
||||||
|
_new = obj(cl, name, **kw)
|
||||||
|
await _new.save()
|
||||||
|
except e.PhiEntryExists as err:
|
||||||
|
print(f"Failed add: {repr(err)}")
|
||||||
|
|
||||||
|
|
||||||
|
async def safe_search(group, name):
|
||||||
|
try:
|
||||||
|
res = await group.search(name)
|
||||||
|
print("Search result:", res)
|
||||||
|
return res
|
||||||
|
except e.PhiEntryDoesNotExist as err:
|
||||||
|
print(f"Failed search: {repr(err)}")
|
||||||
|
|
||||||
|
|
||||||
|
async def safe_delete(obj, cascade=None):
|
||||||
|
try:
|
||||||
|
if cascade:
|
||||||
|
obj.delete_cascade = cascade
|
||||||
|
await obj.delete()
|
||||||
|
except Exception as err:
|
||||||
|
print(f"Failed delete: {repr(err)}")
|
||||||
|
|
||||||
|
|
||||||
|
async def add_member(group, member):
|
||||||
|
await group.add_member(member)
|
||||||
|
|
||||||
|
|
||||||
|
async def remove_member(group, member):
|
||||||
|
await group.remove_member(member)
|
||||||
|
|
||||||
|
|
||||||
|
# asyncio.run(safe_search(h, "pippo"))
|
||||||
|
# asyncio.run(
|
||||||
|
# add_new(User, "pippo", cn="Pippo (Goofy)", sn="Pippo", mail="pippo@unit.info")
|
||||||
|
# )
|
||||||
|
# asyncio.run(safe_search(h, "pippo"))
|
||||||
|
# asyncio.run(
|
||||||
|
# add_new(User, "pippo", cn="Pippo (Goofy)", sn="Pippo", mail="pippo@unit.net")
|
||||||
|
# )
|
||||||
|
# asyncio.run(safe_delete(asyncio.run(safe_search(h, "pippo"))))
|
||||||
|
# asyncio.run(print_async("Hackers members:", get_members(h)))
|
||||||
|
# asyncio.run(safe_delete(h))
|
||||||
|
# asyncio.run(print_async("Hackers members:", get_members(h)))
|
||||||
|
# asyncio.run(safe_delete(h, True))
|
||||||
|
# asyncio.run(print_async("Hackers members:", get_members(h)))
|
||||||
|
|
||||||
|
# asyncio.run(safe_search(r, "phi"))
|
||||||
|
# asyncio.run(print_async("Robots members:", get_members(r)))
|
||||||
|
# asyncio.run(add_new(Service, "db", userPassword="lolpassword"))
|
||||||
|
# asyncio.run(print_async("Robots members:", get_members(r)))
|
||||||
|
|
||||||
|
asyncio.run(safe_search(c, "GitUsers"))
|
||||||
|
asyncio.run(print_async("Congregations members:", get_members(c)))
|
||||||
|
asyncio.run(
|
||||||
|
add_new(Group, "naughty", member=[User(cl, "conte_mascetti"), User(cl, "necchi")])
|
||||||
|
)
|
||||||
|
asyncio.run(print_async("Congregations members:", get_members(c)))
|
||||||
|
asyncio.run(safe_delete(Group(cl, "naughty")))
|
||||||
|
asyncio.run(print_async("Congregations members:", get_members(c)))
|
||||||
|
asyncio.run(
|
||||||
|
add_new(Group, "naughty", member=[User(cl, "conte_mascetti"), User(cl, "necchi")])
|
||||||
|
)
|
||||||
|
asyncio.run(print_async("Congregations members:", get_members(c)))
|
||||||
|
print("==> HERE <==")
|
||||||
|
naughty = sync_await(Group(cl, "naughty").sync())
|
||||||
|
print("NAUGHTY =>>", [m for m in naughty.get_members()])
|
||||||
|
asyncio.run(add_member(naughty, User(cl, "perozzi")))
|
||||||
|
print("NAUGHTY =>>", [m for m in naughty.get_members()])
|
||||||
|
asyncio.run(remove_member(naughty, User(cl, "conte_mascetti")))
|
||||||
|
print("NAUGHTY =>>", [m for m in naughty.get_members()])
|
||||||
|
|
Loading…
Reference in New Issue
Block a user