diff --git a/async_tests/test_async_ldap_model.py b/async_tests/test_async_ldap_model.py index 12f41c3..8a406d5 100644 --- a/async_tests/test_async_ldap_model.py +++ b/async_tests/test_async_ldap_model.py @@ -22,11 +22,13 @@ from phi.async_ldap.model import ( Service, create_new_, User, - PhiUserExists, - PhiUserDoesNotExist, + PhiEntryExists, + PhiEntryDoesNotExist, PhiAttributeMissing, + PhiUnauthorized, Group, ) +from phi.security import hash_pass BASE_DN = "dc=test,dc=abbiamoundominio,dc=org" @@ -41,7 +43,17 @@ EXISTING_USER = [ "sn": ["Existing User"], "mail": ["existing@mail.org"], "uid": ["existing_user"], - "userPassword": ["{SHA}oLY7P6V+DWAMJ9ix7vbMYGIfA+E="], + "userPassword": ["{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g="], + } +] +MISSING_AUTH_USER = [ + { + "dn": f"uid=existing_user,ou=Hackers,{BASE_DN}>", + "objectClass": ["inetOrgPerson", "organizationalPerson", "person", "top"], + "cn": ["exists"], + "sn": ["Existing User"], + "mail": ["existing@mail.org"], + "uid": ["existing_user"], } ] @@ -51,6 +63,7 @@ class MockClient: self._base_dn = base_dn self.args = args self.called_with_args = {} + self.username = f"uid=mock_connection,ou=Services,{base_dn}" @property def base_dn(self): @@ -72,6 +85,8 @@ class MockClient: return EXISTING_USER elif f"uid=not_existing,ou=Hackers,{BASE_DN}" in args: return [] + elif f"uid=missing_auth_user,ou=Hackers,{BASE_DN}" in args: + return MISSING_AUTH_USER conn.search = _search @@ -446,15 +461,19 @@ def test_User_singleton(client_fixture): async def test_User_create_existing(client_fixture): c = User("existing_user", client_fixture) - with pytest.raises(PhiUserExists): - await c.create("existing@mail.org", sn="exists", cn="Existing User") + with pytest.raises(PhiEntryExists) as e: + await c.create( + "existing@mail.org", password="password", sn="exists", cn="Existing User" + ) + + assert c.dn in str(e.value) @pytest.mark.asyncio async def test_User_create_not_existing(client_fixture): c = User("not_existing", client_fixture) - await c.create("not@existing.org") + await c.create("not@existing.org", "password") assert client_fixture.called_with_args["search"]["args"] == (c.dn, 0) assert c._entry["mail"][0] == "not@existing.org" @@ -477,9 +496,10 @@ async def test_User_sync_existing(client_fixture, caplog): async def test_User_sync_not_existing(client_fixture, caplog): c = User("not_existing", client_fixture) - with pytest.raises(PhiUserDoesNotExist): + with pytest.raises(PhiEntryDoesNotExist) as e: await c.sync() + assert c.dn in str(e.value) assert client_fixture.called_with_args["search"]["args"] == (c.dn, 0) diff --git a/openldap/init.ldif b/openldap/init.ldif index b92298f..6579bb4 100644 --- a/openldap/init.ldif +++ b/openldap/init.ldif @@ -67,7 +67,7 @@ cn: Guido sn: Necchi mail: gnecchi@autistici.org uid: necchi -userPassword: {SHA}IZiNWojlNxrWIkk23XDh0svKY14= +userPassword: {SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g= dn: uid=perozzi,ou=Hackers,dc=unit,dc=macaomilano,dc=org objectClass: inetOrgPerson diff --git a/setup.py b/setup.py index d55447c..99b053b 100644 --- a/setup.py +++ b/setup.py @@ -2,21 +2,25 @@ from setuptools import setup, find_packages setup( - name='phi', - version='0.0.1', - - description='Post-Human Interface', + name="phi", + version="0.0.1", + description="Post-Human Interface", # license='', - url='https://git.abbiamoundominio.org/unit/phi', - - author='unit', - author_email='unit@paranoici.org', - - package_dir={'': 'src'}, + url="https://git.abbiamoundominio.org/unit/phi", + author="unit", + author_email="unit@paranoici.org", + package_dir={"": "src"}, packages=find_packages("src"), entry_points={"console_scripts": ["phid=phi.app:cli"]}, - - setup_requires=['pytest-runner'], - install_requires=['aiohttp==2.3.8', 'click==7.0', 'pyYAML', 'ldap3', 'bonsai==1.1.0'], - tests_require=['pytest', 'pytest-aiohttp', 'pytest-asyncio', 'async-generator'] + setup_requires=["pytest-runner"], + install_requires=[ + "aiohttp==2.3.8", + "click==7.0", + "pyYAML", + "ldap3", + "bonsai==1.1.0", + "passlib==1.7.1", + "bcrypt==3.1.7", + ], + tests_require=["pytest", "pytest-aiohttp", "pytest-asyncio", "async-generator"], ) diff --git a/src/phi/async_ldap/model.py b/src/phi/async_ldap/model.py index 80d9615..b233a90 100644 --- a/src/phi/async_ldap/model.py +++ b/src/phi/async_ldap/model.py @@ -7,6 +7,7 @@ import typing as T from bonsai import LDAPEntry, LDAPModOp, NoSuchObjectError from phi.logging import get_logger +from phi.security import hash_pass log = get_logger(__name__) @@ -253,14 +254,17 @@ class User(Hackers): def name(self, name): raise RuntimeError("Name property is not modifiable.") - async def create(self, mail, sn=None, cn=None): + async def create(self, mail, password=None, sn=None, cn=None): async with self.client.connect(is_async=True) as conn: res = await conn.search(self.dn, 0) if len(res) > 0: raise PhiEntryExists(self.dn) _sn = sn if sn is not None else self.name _cn = cn if cn is not None else self.name - self._entry = await create_new_(self, uid=self.name, mail=mail, sn=_sn, cn=_cn) + hashed = hash_pass(password) + self._entry = await create_new_( + self, uid=self.name, mail=mail, sn=_sn, cn=_cn, userPassword=hashed + ) async def sync(self): async with self.client.connect(is_async=True) as conn: diff --git a/src/phi/security.py b/src/phi/security.py new file mode 100644 index 0000000..8bcfc92 --- /dev/null +++ b/src/phi/security.py @@ -0,0 +1,23 @@ +# -*- encoding: utf-8 -*- +from passlib.hash import ( + ldap_sha1, + ldap_bcrypt, + ldap_sha256_crypt, + ldap_sha512_crypt, + ldap_pbkdf2_sha256, + ldap_pbkdf2_sha512, +) + + +HASH_CALLABLE = { + "sha1": ldap_sha1.hash, + "bcrypt": ldap_bcrypt.hash, + "sha256_crypt": ldap_sha256_crypt.hash, + "sha512_crypt": ldap_sha512_crypt.hash, + "pbkdf2_sha256": ldap_pbkdf2_sha256.hash, + "pbkdf2_sha512": ldap_pbkdf2_sha512.hash, +} + + +def hash_pass(password, method="sha1"): + return HASH_CALLABLE[method](password)