sources/ldap: improve unittests

This commit is contained in:
Jens Langhammer 2020-07-10 20:10:51 +02:00
parent c191b62245
commit 8de3c4fbd6
5 changed files with 104 additions and 46 deletions

View file

@ -1,6 +1,5 @@
[run] [run]
source = passbook source = passbook
branch = True
omit = omit =
*/wsgi.py */wsgi.py
manage.py manage.py

View file

@ -18,8 +18,7 @@ class LDAPBackend(ModelBackend):
return None return None
for source in LDAPSource.objects.filter(enabled=True): for source in LDAPSource.objects.filter(enabled=True):
LOGGER.debug("LDAP Auth attempt", source=source) LOGGER.debug("LDAP Auth attempt", source=source)
_ldap = Connector(source) user = Connector(source).auth_user(**kwargs)
user = _ldap.auth_user(**kwargs)
if user: if user:
return user return user
return None return None

View file

@ -53,18 +53,19 @@ class Connector:
) )
for group in groups: for group in groups:
attributes = group.get("attributes", {}) attributes = group.get("attributes", {})
if self._source.object_uniqueness_field not in attributes:
LOGGER.warning(
"Cannot find uniqueness Field in attributes", user=attributes.keys()
)
continue
uniq = attributes[self._source.object_uniqueness_field]
_, created = Group.objects.update_or_create( _, created = Group.objects.update_or_create(
attributes__ldap_uniq=attributes.get( attributes__ldap_uniq=uniq,
self._source.object_uniqueness_field, ""
),
parent=self._source.sync_parent_group, parent=self._source.sync_parent_group,
# defaults=self._build_object_properties(attributes),
defaults={ defaults={
"name": attributes.get("name", ""), "name": attributes.get("name", ""),
"attributes": { "attributes": {
"ldap_uniq": attributes.get( "ldap_uniq": uniq,
self._source.object_uniqueness_field, ""
),
"distinguishedName": attributes.get("distinguishedName"), "distinguishedName": attributes.get("distinguishedName"),
}, },
}, },
@ -86,11 +87,12 @@ class Connector:
) )
for user in users: for user in users:
attributes = user.get("attributes", {}) attributes = user.get("attributes", {})
try: if self._source.object_uniqueness_field not in attributes:
uniq = attributes[self._source.object_uniqueness_field] LOGGER.warning(
except KeyError: "Cannot find uniqueness Field in attributes", user=user.keys()
LOGGER.warning("Cannot find uniqueness Field in attributes") )
continue continue
uniq = attributes[self._source.object_uniqueness_field]
try: try:
defaults = self._build_object_properties(attributes) defaults = self._build_object_properties(attributes)
user, created = User.objects.update_or_create( user, created = User.objects.update_or_create(
@ -180,7 +182,7 @@ class Connector:
) )
return properties return properties
def auth_user(self, password: str, **filters: Dict[str, str]) -> Optional[User]: def auth_user(self, password: str, **filters: str) -> Optional[User]:
"""Try to bind as either user_dn or mail with password. """Try to bind as either user_dn or mail with password.
Returns True on success, otherwise False""" Returns True on success, otherwise False"""
users = User.objects.filter(**filters) users = User.objects.filter(**filters)

View file

@ -4,22 +4,6 @@ from passbook.sources.ldap.connector import Connector
from passbook.sources.ldap.models import LDAPSource from passbook.sources.ldap.models import LDAPSource
@CELERY_APP.task()
def sync_groups(source_pk: int):
"""Sync LDAP Groups on background worker"""
source = LDAPSource.objects.get(pk=source_pk)
connector = Connector(source)
connector.sync_groups()
@CELERY_APP.task()
def sync_users(source_pk: int):
"""Sync LDAP Users on background worker"""
source = LDAPSource.objects.get(pk=source_pk)
connector = Connector(source)
connector.sync_users()
@CELERY_APP.task() @CELERY_APP.task()
def sync(): def sync():
"""Sync all sources""" """Sync all sources"""

View file

@ -1,12 +1,15 @@
"""LDAP Source tests""" """LDAP Source tests"""
from unittest.mock import PropertyMock, patch from unittest.mock import Mock, PropertyMock, patch
from django.test import TestCase from django.test import TestCase
from ldap3 import MOCK_SYNC, OFFLINE_AD_2012_R2, Connection, Server from ldap3 import MOCK_SYNC, OFFLINE_AD_2012_R2, Connection, Server
from oauth2_provider.generators import generate_client_secret
from passbook.core.models import User from passbook.core.models import Group, User
from passbook.sources.ldap.auth import LDAPBackend
from passbook.sources.ldap.connector import Connector from passbook.sources.ldap.connector import Connector
from passbook.sources.ldap.models import LDAPPropertyMapping, LDAPSource from passbook.sources.ldap.models import LDAPPropertyMapping, LDAPSource
from passbook.sources.ldap.tasks import sync
def _build_mock_connection() -> Connection: def _build_mock_connection() -> Connection:
@ -20,30 +23,64 @@ def _build_mock_connection() -> Connection:
client_strategy=MOCK_SYNC, client_strategy=MOCK_SYNC,
) )
connection.strategy.add_entry( connection.strategy.add_entry(
"cn=user0,ou=test,o=lab", "cn=group1,ou=groups,ou=test,o=lab",
{ {
"userPassword": "test0000", "name": "test-group",
"sAMAccountName": "user0_sn", "objectSid": "unique-test-group",
"revision": 0, "objectCategory": "Group",
"objectSid": "unique-test0000", "distinguishedName": "cn=group1,ou=groups,ou=test,o=lab",
"objectCategory": "Person", },
)
# Group without SID
connection.strategy.add_entry(
"cn=group2,ou=groups,ou=test,o=lab",
{
"name": "test-group",
"objectCategory": "Group",
"distinguishedName": "cn=group2,ou=groups,ou=test,o=lab",
}, },
) )
connection.strategy.add_entry( connection.strategy.add_entry(
"cn=user1,ou=test,o=lab", "cn=user0,ou=users,ou=test,o=lab",
{
"userPassword": LDAP_PASSWORD,
"sAMAccountName": "user0_sn",
"name": "user0_sn",
"revision": 0,
"objectSid": "user0",
"objectCategory": "Person",
"memberOf": "cn=group1,ou=groups,ou=test,o=lab",
},
)
# User without SID
connection.strategy.add_entry(
"cn=user1,ou=users,ou=test,o=lab",
{ {
"userPassword": "test1111", "userPassword": "test1111",
"sAMAccountName": "user1_sn", "sAMAccountName": "user2_sn",
"name": "user1_sn",
"revision": 0, "revision": 0,
"objectSid": "unique-test1111",
"objectCategory": "Person", "objectCategory": "Person",
}, },
) )
# Duplicate users
connection.strategy.add_entry( connection.strategy.add_entry(
"cn=user2,ou=test,o=lab", "cn=user2,ou=users,ou=test,o=lab",
{ {
"userPassword": "test2222", "userPassword": "test2222",
"sAMAccountName": "user2_sn", "sAMAccountName": "user2_sn",
"name": "user2_sn",
"revision": 0,
"objectSid": "unique-test2222",
"objectCategory": "Person",
},
)
connection.strategy.add_entry(
"cn=user3,ou=users,ou=test,o=lab",
{
"userPassword": "test2222",
"sAMAccountName": "user2_sn",
"name": "user2_sn",
"revision": 0, "revision": 0,
"objectSid": "unique-test2222", "objectSid": "unique-test2222",
"objectCategory": "Person", "objectCategory": "Person",
@ -53,6 +90,7 @@ def _build_mock_connection() -> Connection:
return connection return connection
LDAP_PASSWORD = generate_client_secret()
LDAP_CONNECTION_PATCH = PropertyMock(return_value=_build_mock_connection()) LDAP_CONNECTION_PATCH = PropertyMock(return_value=_build_mock_connection())
@ -61,7 +99,11 @@ class LDAPSourceTests(TestCase):
def setUp(self): def setUp(self):
self.source = LDAPSource.objects.create( self.source = LDAPSource.objects.create(
name="ldap", slug="ldap", base_dn="o=lab" name="ldap",
slug="ldap",
base_dn="ou=test,o=lab",
additional_user_dn="ou=users",
additional_group_dn="ou=groups",
) )
self.source.property_mappings.set(LDAPPropertyMapping.objects.all()) self.source.property_mappings.set(LDAPPropertyMapping.objects.all())
self.source.save() self.source.save()
@ -71,5 +113,37 @@ class LDAPSourceTests(TestCase):
"""Test user sync""" """Test user sync"""
connector = Connector(self.source) connector = Connector(self.source)
connector.sync_users() connector.sync_users()
user = User.objects.filter(username="user2_sn") self.assertTrue(User.objects.filter(username="user0_sn").exists())
self.assertTrue(user.exists()) self.assertFalse(User.objects.filter(username="user1_sn").exists())
@patch("passbook.sources.ldap.models.LDAPSource.connection", LDAP_CONNECTION_PATCH)
def test_sync_groups(self):
"""Test group sync"""
connector = Connector(self.source)
connector.sync_groups()
connector.sync_membership()
group = Group.objects.filter(name="test-group")
self.assertTrue(group.exists())
@patch("passbook.sources.ldap.models.LDAPSource.connection", LDAP_CONNECTION_PATCH)
def test_auth(self):
"""Test Cached auth"""
connector = Connector(self.source)
connector.sync_users()
user = User.objects.get(username="user0_sn")
auth_user_by_bind = Mock(return_value=user)
with patch(
"passbook.sources.ldap.connector.Connector.auth_user_by_bind",
auth_user_by_bind,
):
backend = LDAPBackend()
self.assertEqual(
backend.authenticate(None, username="user0_sn", password=LDAP_PASSWORD),
user,
)
@patch("passbook.sources.ldap.models.LDAPSource.connection", LDAP_CONNECTION_PATCH)
def test_tasks(self):
"""Test Scheduled tasks"""
sync()