forked from Github-Mirrors/canaille

Previously we used the uid since we supposed this value was always valid, but some users user the mail attribute as the User RDN in their OpenLDAP installation, and do not have a uuid.
291 lines
8.7 KiB
Python
291 lines
8.7 KiB
Python
import datetime
|
|
from unittest import mock
|
|
|
|
import ldap.dn
|
|
import pytest
|
|
from canaille.app import models
|
|
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
|
|
|
|
|
|
def test_object_creation(app, backend):
|
|
user = models.User(
|
|
formatted_name="Doe", # leading space
|
|
family_name="Doe",
|
|
user_name="user",
|
|
emails="john@doe.com",
|
|
)
|
|
assert not user.exists
|
|
user.save()
|
|
assert user.exists
|
|
|
|
user = models.User.get(id=user.id)
|
|
assert user.exists
|
|
|
|
user.delete()
|
|
|
|
|
|
def test_repr(backend, foo_group, user):
|
|
assert repr(foo_group) == "<Group display_name=foo>"
|
|
assert repr(user) == "<User user_name=user>"
|
|
|
|
|
|
def test_dn_when_leading_space_in_id_attribute(backend):
|
|
user = models.User(
|
|
formatted_name=" Doe", # leading space
|
|
family_name="Doe",
|
|
user_name="user",
|
|
emails="john@doe.com",
|
|
)
|
|
user.save()
|
|
|
|
assert ldap.dn.is_dn(user.dn)
|
|
assert ldap.dn.dn2str(ldap.dn.str2dn(user.dn)) == user.dn
|
|
assert user.dn == "uid=user,ou=users,dc=mydomain,dc=tld"
|
|
|
|
user.delete()
|
|
|
|
|
|
def test_dn_when_ldap_special_char_in_id_attribute(backend):
|
|
user = models.User(
|
|
formatted_name="#Doe", # special char
|
|
family_name="Doe",
|
|
user_name="#user",
|
|
emails="john@doe.com",
|
|
)
|
|
user.save()
|
|
|
|
assert ldap.dn.is_dn(user.dn)
|
|
assert ldap.dn.dn2str(ldap.dn.str2dn(user.dn)) == user.dn
|
|
assert user.dn == "uid=\\#user,ou=users,dc=mydomain,dc=tld"
|
|
|
|
user.delete()
|
|
|
|
|
|
def test_filter(backend, foo_group, bar_group):
|
|
assert models.Group.query(display_name="foo") == [foo_group]
|
|
assert models.Group.query(display_name="bar") == [bar_group]
|
|
|
|
assert models.Group.query(display_name="foo") != 3
|
|
|
|
assert models.Group.query(display_name=["foo"]) == [foo_group]
|
|
assert models.Group.query(display_name=["bar"]) == [bar_group]
|
|
|
|
assert set(models.Group.query(display_name=["foo", "bar"])) == {
|
|
foo_group,
|
|
bar_group,
|
|
}
|
|
|
|
|
|
def test_ldap_to_python():
|
|
assert (
|
|
python_to_ldap(
|
|
datetime.datetime(2000, 1, 2, 3, 4, 5, tzinfo=datetime.timezone.utc),
|
|
Syntax.GENERALIZED_TIME,
|
|
)
|
|
== b"20000102030405Z"
|
|
)
|
|
assert (
|
|
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"
|
|
)
|
|
|
|
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
|
|
) == 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)),
|
|
)
|
|
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"
|
|
|
|
|
|
def test_operational_attribute_conversion(backend):
|
|
assert "oauthClientName" in LDAPObject.ldap_object_attributes(backend)
|
|
assert "invalidAttribute" not in LDAPObject.ldap_object_attributes(backend)
|
|
|
|
assert python_attrs_to_ldap(
|
|
{
|
|
"oauthClientName": ["foobar_name"],
|
|
"invalidAttribute": ["foobar"],
|
|
}
|
|
) == {
|
|
"oauthClientName": [b"foobar_name"],
|
|
"invalidAttribute": [b"foobar"],
|
|
}
|
|
|
|
|
|
def test_guess_object_from_dn(backend, testclient, foo_group):
|
|
foo_group.members = [foo_group]
|
|
foo_group.save()
|
|
g = LDAPObject.get(id=foo_group.dn)
|
|
assert isinstance(g, models.Group)
|
|
assert g == foo_group
|
|
assert g.cn == foo_group.cn
|
|
|
|
ou = LDAPObject.get(id=f"{models.Group.base},{models.Group.root_dn}")
|
|
assert isinstance(ou, LDAPObject)
|
|
|
|
|
|
def test_object_class_update(backend, testclient):
|
|
testclient.app.config["BACKENDS"]["LDAP"]["USER_CLASS"] = ["inetOrgPerson"]
|
|
setup_ldap_models(testclient.app.config)
|
|
|
|
user1 = models.User(cn="foo1", sn="bar1", user_name="baz1")
|
|
user1.save()
|
|
|
|
assert user1.objectClass == ["inetOrgPerson"]
|
|
assert models.User.get(id=user1.id).objectClass == ["inetOrgPerson"]
|
|
|
|
testclient.app.config["BACKENDS"]["LDAP"]["USER_CLASS"] = [
|
|
"inetOrgPerson",
|
|
"extensibleObject",
|
|
]
|
|
setup_ldap_models(testclient.app.config)
|
|
|
|
user2 = models.User(cn="foo2", sn="bar2", user_name="baz2")
|
|
user2.save()
|
|
|
|
assert user2.objectClass == ["inetOrgPerson", "extensibleObject"]
|
|
assert models.User.get(id=user2.id).objectClass == [
|
|
"inetOrgPerson",
|
|
"extensibleObject",
|
|
]
|
|
|
|
user1 = models.User.get(id=user1.id)
|
|
assert user1.objectClass == ["inetOrgPerson"]
|
|
|
|
user1.save()
|
|
assert user1.objectClass == ["inetOrgPerson", "extensibleObject"]
|
|
assert models.User.get(id=user1.id).objectClass == [
|
|
"inetOrgPerson",
|
|
"extensibleObject",
|
|
]
|
|
|
|
user1.delete()
|
|
user2.delete()
|
|
|
|
|
|
def test_ldap_connection_no_remote(testclient, configuration):
|
|
validate(configuration)
|
|
|
|
|
|
def test_ldap_connection_remote(testclient, configuration, backend):
|
|
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)
|
|
|
|
|
|
def test_ldap_cannot_create_users(testclient, configuration, backend):
|
|
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)
|
|
|
|
|
|
def test_ldap_cannot_create_groups(testclient, configuration, backend):
|
|
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)
|
|
|
|
|
|
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"
|