2022-12-14 23:03:01 +00:00
|
|
|
import datetime
|
2023-04-08 18:09:52 +00:00
|
|
|
from unittest import mock
|
2022-12-14 23:03:01 +00:00
|
|
|
|
2022-03-02 17:31:48 +00:00
|
|
|
import ldap.dn
|
2023-04-08 18:09:52 +00:00
|
|
|
import pytest
|
2023-04-09 09:37:04 +00:00
|
|
|
from canaille.app import models
|
2023-04-08 18:09:52 +00:00
|
|
|
from canaille.app.configuration import ConfigurationException
|
|
|
|
from canaille.app.configuration import validate
|
|
|
|
from canaille.backends.ldap.backend import setup_ldap_models
|
|
|
|
from canaille.backends.ldap.ldapobject import LDAPObject
|
|
|
|
from canaille.backends.ldap.ldapobject import python_attrs_to_ldap
|
|
|
|
from canaille.backends.ldap.utils import ldap_to_python
|
|
|
|
from canaille.backends.ldap.utils import python_to_ldap
|
|
|
|
from canaille.backends.ldap.utils import Syntax
|
2021-06-03 13:00:11 +00:00
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_object_creation(app, backend):
|
2023-04-09 09:37:04 +00:00
|
|
|
user = models.User(
|
2023-02-05 17:57:18 +00:00
|
|
|
formatted_name="Doe", # leading space
|
|
|
|
family_name="Doe",
|
|
|
|
user_name="user",
|
2023-06-22 13:14:07 +00:00
|
|
|
emails="john@doe.com",
|
2023-03-09 12:00:17 +00:00
|
|
|
)
|
|
|
|
assert not user.exists
|
|
|
|
user.save()
|
|
|
|
assert user.exists
|
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
user = models.User.get(id=user.id)
|
2023-03-09 12:00:17 +00:00
|
|
|
assert user.exists
|
|
|
|
|
|
|
|
user.delete()
|
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_repr(backend, foo_group, user):
|
2023-02-05 18:39:52 +00:00
|
|
|
assert repr(foo_group) == "<Group display_name=foo>"
|
2023-02-05 17:57:18 +00:00
|
|
|
assert repr(user) == "<User formatted_name=John (johnny) Doe>"
|
2022-12-14 20:06:59 +00:00
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_dn_when_leading_space_in_id_attribute(backend):
|
2023-04-09 09:37:04 +00:00
|
|
|
user = models.User(
|
2023-02-05 17:57:18 +00:00
|
|
|
formatted_name=" Doe", # leading space
|
|
|
|
family_name="Doe",
|
|
|
|
user_name="user",
|
2023-06-22 13:14:07 +00:00
|
|
|
emails="john@doe.com",
|
2022-03-02 17:31:48 +00:00
|
|
|
)
|
2022-05-08 14:31:17 +00:00
|
|
|
user.save()
|
2022-03-02 17:31:48 +00:00
|
|
|
|
|
|
|
assert ldap.dn.is_dn(user.dn)
|
|
|
|
assert ldap.dn.dn2str(ldap.dn.str2dn(user.dn)) == user.dn
|
2022-11-05 18:52:57 +00:00
|
|
|
assert user.dn == "cn=Doe,ou=users,dc=mydomain,dc=tld"
|
2022-03-02 17:31:48 +00:00
|
|
|
|
2022-05-08 14:31:17 +00:00
|
|
|
user.delete()
|
2022-05-18 09:31:26 +00:00
|
|
|
|
2022-03-02 17:31:48 +00:00
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_dn_when_ldap_special_char_in_id_attribute(backend):
|
2023-04-09 09:37:04 +00:00
|
|
|
user = models.User(
|
2023-02-05 17:57:18 +00:00
|
|
|
formatted_name="#Doe", # special char
|
|
|
|
family_name="Doe",
|
|
|
|
user_name="user",
|
2023-06-22 13:14:07 +00:00
|
|
|
emails="john@doe.com",
|
2022-03-02 17:31:48 +00:00
|
|
|
)
|
2022-05-08 14:31:17 +00:00
|
|
|
user.save()
|
2022-03-02 17:31:48 +00:00
|
|
|
|
|
|
|
assert ldap.dn.is_dn(user.dn)
|
|
|
|
assert ldap.dn.dn2str(ldap.dn.str2dn(user.dn)) == user.dn
|
2022-11-05 18:52:57 +00:00
|
|
|
assert user.dn == "cn=\\#Doe,ou=users,dc=mydomain,dc=tld"
|
2022-05-18 09:31:26 +00:00
|
|
|
|
2022-05-08 14:31:17 +00:00
|
|
|
user.delete()
|
2022-12-14 23:03:01 +00:00
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_filter(backend, foo_group, bar_group):
|
2023-04-09 09:37:04 +00:00
|
|
|
assert models.Group.query(display_name="foo") == [foo_group]
|
|
|
|
assert models.Group.query(display_name="bar") == [bar_group]
|
2022-12-14 23:15:10 +00:00
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
assert models.Group.query(display_name="foo") != 3
|
2023-03-02 17:35:26 +00:00
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
assert models.Group.query(display_name=["foo"]) == [foo_group]
|
|
|
|
assert models.Group.query(display_name=["bar"]) == [bar_group]
|
2022-12-14 23:15:10 +00:00
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
assert set(models.Group.query(display_name=["foo", "bar"])) == {
|
|
|
|
foo_group,
|
|
|
|
bar_group,
|
|
|
|
}
|
2022-12-14 23:15:10 +00:00
|
|
|
|
|
|
|
|
2022-12-14 23:03:01 +00:00
|
|
|
def test_ldap_to_python():
|
|
|
|
assert (
|
2023-03-17 23:38:56 +00:00
|
|
|
python_to_ldap(
|
|
|
|
datetime.datetime(2000, 1, 2, 3, 4, 5, tzinfo=datetime.timezone.utc),
|
|
|
|
Syntax.GENERALIZED_TIME,
|
|
|
|
)
|
|
|
|
== b"20000102030405Z"
|
2022-12-14 23:03:01 +00:00
|
|
|
)
|
|
|
|
assert (
|
2023-03-17 23:38:56 +00:00
|
|
|
python_to_ldap(
|
|
|
|
datetime.datetime(
|
|
|
|
2000,
|
|
|
|
1,
|
|
|
|
2,
|
|
|
|
3,
|
|
|
|
4,
|
|
|
|
5,
|
|
|
|
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=79200)),
|
|
|
|
),
|
|
|
|
Syntax.GENERALIZED_TIME,
|
|
|
|
)
|
|
|
|
== b"20000102030405-0200"
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
python_to_ldap(datetime.datetime.min, Syntax.GENERALIZED_TIME)
|
|
|
|
== b"000001010000Z"
|
2022-12-14 23:03:01 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
assert python_to_ldap(1337, Syntax.INTEGER) == b"1337"
|
|
|
|
|
|
|
|
assert python_to_ldap(True, Syntax.BOOLEAN) == b"TRUE"
|
|
|
|
assert python_to_ldap(False, Syntax.BOOLEAN) == b"FALSE"
|
|
|
|
|
|
|
|
assert python_to_ldap("foobar", Syntax.DIRECTORY_STRING) == b"foobar"
|
|
|
|
|
|
|
|
assert python_to_ldap(b"foobar", Syntax.JPEG) == b"foobar"
|
|
|
|
|
|
|
|
|
|
|
|
def test_python_to_ldap():
|
|
|
|
assert ldap_to_python(
|
|
|
|
b"20000102030405Z", Syntax.GENERALIZED_TIME
|
2023-03-17 23:38:56 +00:00
|
|
|
) == datetime.datetime(2000, 1, 2, 3, 4, 5, tzinfo=datetime.timezone.utc)
|
|
|
|
assert ldap_to_python(
|
|
|
|
b"20000102030405-0200", Syntax.GENERALIZED_TIME
|
|
|
|
) == datetime.datetime(
|
|
|
|
2000,
|
|
|
|
1,
|
|
|
|
2,
|
|
|
|
3,
|
|
|
|
4,
|
|
|
|
5,
|
|
|
|
tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=79200)),
|
|
|
|
)
|
2022-12-14 23:03:01 +00:00
|
|
|
assert (
|
|
|
|
ldap_to_python(b"000001010000Z", Syntax.GENERALIZED_TIME)
|
|
|
|
== datetime.datetime.min
|
|
|
|
)
|
|
|
|
|
|
|
|
assert ldap_to_python(b"1337", Syntax.INTEGER) == 1337
|
|
|
|
|
|
|
|
assert ldap_to_python(b"TRUE", Syntax.BOOLEAN) is True
|
|
|
|
assert ldap_to_python(b"FALSE", Syntax.BOOLEAN) is False
|
|
|
|
|
|
|
|
assert ldap_to_python(b"foobar", Syntax.DIRECTORY_STRING) == "foobar"
|
|
|
|
|
|
|
|
assert ldap_to_python(b"foobar", Syntax.JPEG) == b"foobar"
|
2022-12-15 11:41:31 +00:00
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_operational_attribute_conversion(backend):
|
|
|
|
assert "oauthClientName" in LDAPObject.ldap_object_attributes(backend)
|
|
|
|
assert "invalidAttribute" not in LDAPObject.ldap_object_attributes(backend)
|
2022-12-15 11:41:31 +00:00
|
|
|
|
2023-03-09 00:14:07 +00:00
|
|
|
assert python_attrs_to_ldap(
|
2022-12-15 11:41:31 +00:00
|
|
|
{
|
|
|
|
"oauthClientName": ["foobar_name"],
|
|
|
|
"invalidAttribute": ["foobar"],
|
|
|
|
}
|
|
|
|
) == {
|
|
|
|
"oauthClientName": [b"foobar_name"],
|
|
|
|
"invalidAttribute": [b"foobar"],
|
|
|
|
}
|
2023-03-08 22:53:53 +00:00
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_guess_object_from_dn(backend, testclient, foo_group):
|
2023-05-12 22:24:24 +00:00
|
|
|
foo_group.members = [foo_group]
|
2023-03-08 22:53:53 +00:00
|
|
|
foo_group.save()
|
2023-02-05 18:08:25 +00:00
|
|
|
g = LDAPObject.get(id=foo_group.dn)
|
2023-04-09 09:37:04 +00:00
|
|
|
assert isinstance(g, models.Group)
|
2023-03-08 22:53:53 +00:00
|
|
|
assert g == foo_group
|
|
|
|
assert g.cn == foo_group.cn
|
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
ou = LDAPObject.get(id=f"{models.Group.base},{models.Group.root_dn}")
|
2023-05-12 22:24:24 +00:00
|
|
|
assert isinstance(ou, LDAPObject)
|
2023-03-11 18:46:12 +00:00
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_object_class_update(backend, testclient):
|
2023-04-10 18:31:54 +00:00
|
|
|
testclient.app.config["BACKENDS"]["LDAP"]["USER_CLASS"] = ["inetOrgPerson"]
|
2023-03-11 18:46:12 +00:00
|
|
|
setup_ldap_models(testclient.app.config)
|
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
user1 = models.User(cn="foo1", sn="bar1")
|
2023-03-11 18:46:12 +00:00
|
|
|
user1.save()
|
|
|
|
|
|
|
|
assert user1.objectClass == ["inetOrgPerson"]
|
2023-04-09 09:37:04 +00:00
|
|
|
assert models.User.get(id=user1.id).objectClass == ["inetOrgPerson"]
|
2023-03-11 18:46:12 +00:00
|
|
|
|
2023-04-10 18:31:54 +00:00
|
|
|
testclient.app.config["BACKENDS"]["LDAP"]["USER_CLASS"] = [
|
|
|
|
"inetOrgPerson",
|
|
|
|
"extensibleObject",
|
|
|
|
]
|
2023-03-11 18:46:12 +00:00
|
|
|
setup_ldap_models(testclient.app.config)
|
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
user2 = models.User(cn="foo2", sn="bar2")
|
2023-03-11 18:46:12 +00:00
|
|
|
user2.save()
|
|
|
|
|
|
|
|
assert user2.objectClass == ["inetOrgPerson", "extensibleObject"]
|
2023-04-09 09:37:04 +00:00
|
|
|
assert models.User.get(id=user2.id).objectClass == [
|
|
|
|
"inetOrgPerson",
|
|
|
|
"extensibleObject",
|
|
|
|
]
|
2023-03-11 18:46:12 +00:00
|
|
|
|
2023-04-09 09:37:04 +00:00
|
|
|
user1 = models.User.get(id=user1.id)
|
2023-03-11 18:46:12 +00:00
|
|
|
assert user1.objectClass == ["inetOrgPerson"]
|
|
|
|
|
|
|
|
user1.save()
|
|
|
|
assert user1.objectClass == ["inetOrgPerson", "extensibleObject"]
|
2023-04-09 09:37:04 +00:00
|
|
|
assert models.User.get(id=user1.id).objectClass == [
|
|
|
|
"inetOrgPerson",
|
|
|
|
"extensibleObject",
|
|
|
|
]
|
2023-04-08 18:14:30 +00:00
|
|
|
|
|
|
|
user1.delete()
|
|
|
|
user2.delete()
|
2023-04-08 18:09:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_ldap_connection_no_remote(testclient, configuration):
|
|
|
|
validate(configuration)
|
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_ldap_connection_remote(testclient, configuration, backend):
|
2023-04-08 18:09:52 +00:00
|
|
|
validate(configuration, validate_remote=True)
|
|
|
|
|
|
|
|
|
|
|
|
def test_ldap_connection_remote_ldap_unreachable(testclient, configuration):
|
|
|
|
configuration["BACKENDS"]["LDAP"]["URI"] = "ldap://invalid-ldap.com"
|
|
|
|
with pytest.raises(
|
|
|
|
ConfigurationException,
|
|
|
|
match=r"Could not connect to the LDAP server",
|
|
|
|
):
|
|
|
|
validate(configuration, validate_remote=True)
|
|
|
|
|
|
|
|
|
|
|
|
def test_ldap_connection_remote_ldap_wrong_credentials(testclient, configuration):
|
|
|
|
configuration["BACKENDS"]["LDAP"]["BIND_PW"] = "invalid-password"
|
|
|
|
with pytest.raises(
|
|
|
|
ConfigurationException,
|
|
|
|
match=r"LDAP authentication failed with user",
|
|
|
|
):
|
|
|
|
validate(configuration, validate_remote=True)
|
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_ldap_cannot_create_users(testclient, configuration, backend):
|
2023-04-08 18:09:52 +00:00
|
|
|
from canaille.core.models import User
|
|
|
|
|
|
|
|
def fake_init(*args, **kwarg):
|
|
|
|
raise ldap.INSUFFICIENT_ACCESS
|
|
|
|
|
|
|
|
with mock.patch.object(User, "__init__", fake_init):
|
|
|
|
with pytest.raises(
|
|
|
|
ConfigurationException,
|
|
|
|
match=r"cannot create users at",
|
|
|
|
):
|
|
|
|
validate(configuration, validate_remote=True)
|
|
|
|
|
|
|
|
|
2023-05-20 15:17:46 +00:00
|
|
|
def test_ldap_cannot_create_groups(testclient, configuration, backend):
|
2023-04-08 18:09:52 +00:00
|
|
|
from canaille.core.models import Group
|
|
|
|
|
|
|
|
def fake_init(*args, **kwarg):
|
|
|
|
raise ldap.INSUFFICIENT_ACCESS
|
|
|
|
|
|
|
|
with mock.patch.object(Group, "__init__", fake_init):
|
|
|
|
with pytest.raises(
|
|
|
|
ConfigurationException,
|
|
|
|
match=r"cannot create groups at",
|
|
|
|
):
|
|
|
|
validate(configuration, validate_remote=True)
|
2023-04-10 11:59:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_login_placeholder(testclient):
|
|
|
|
testclient.app.config["BACKENDS"]["LDAP"]["USER_FILTER"] = "(uid={login})"
|
|
|
|
placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
|
|
|
|
assert placeholder == "jdoe"
|
|
|
|
|
|
|
|
testclient.app.config["BACKENDS"]["LDAP"]["USER_FILTER"] = "(cn={login})"
|
|
|
|
placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
|
|
|
|
assert placeholder == "John Doe"
|
|
|
|
|
|
|
|
testclient.app.config["BACKENDS"]["LDAP"]["USER_FILTER"] = "(mail={login})"
|
|
|
|
placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
|
|
|
|
assert placeholder == "john@doe.com"
|
|
|
|
|
|
|
|
testclient.app.config["BACKENDS"]["LDAP"][
|
|
|
|
"USER_FILTER"
|
|
|
|
] = "(|(uid={login})(mail={login}))"
|
|
|
|
placeholder = testclient.get("/login").form["login"].attrs["placeholder"]
|
|
|
|
assert placeholder == "jdoe or john@doe.com"
|