sources/ldap: allow for anonymous binds, fix sync_users_password not working correctly
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
d9ece98bbc
commit
2592fc3826
|
@ -1,10 +1,13 @@
|
|||
"""Source API Views"""
|
||||
from typing import Any
|
||||
|
||||
from django.http.response import Http404
|
||||
from django.utils.text import slugify
|
||||
from django_filters.filters import AllValuesMultipleFilter
|
||||
from django_filters.filterset import FilterSet
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
@ -20,6 +23,16 @@ from authentik.sources.ldap.models import LDAPPropertyMapping, LDAPSource
|
|||
class LDAPSourceSerializer(SourceSerializer):
|
||||
"""LDAP Source Serializer"""
|
||||
|
||||
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Check that only a single source has password_sync on"""
|
||||
sync_users_password = attrs.get("sync_users_password", True)
|
||||
if sync_users_password:
|
||||
if LDAPSource.objects.filter(sync_users_password=True).exists():
|
||||
raise ValidationError(
|
||||
"Only a single LDAP Source with password synchronization is allowed"
|
||||
)
|
||||
return super().validate(attrs)
|
||||
|
||||
class Meta:
|
||||
model = LDAPSource
|
||||
fields = SourceSerializer.Meta.fields + [
|
||||
|
|
31
authentik/sources/ldap/migrations/0012_auto_20210812_1703.py
Normal file
31
authentik/sources/ldap/migrations/0012_auto_20210812_1703.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
# Generated by Django 3.2.5 on 2021-08-12 17:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("authentik_sources_ldap", "0011_ldapsource_property_mappings_group"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="ldapsource",
|
||||
name="bind_cn",
|
||||
field=models.TextField(blank=True, verbose_name="Bind CN"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ldapsource",
|
||||
name="bind_password",
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="ldapsource",
|
||||
name="sync_users_password",
|
||||
field=models.BooleanField(
|
||||
default=True,
|
||||
help_text="When a user changes their password, sync it back to LDAP. This can only be enabled on a single LDAP source.",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -17,8 +17,8 @@ class LDAPSource(Source):
|
|||
validators=[DomainlessURLValidator(schemes=["ldap", "ldaps"])],
|
||||
verbose_name=_("Server URI"),
|
||||
)
|
||||
bind_cn = models.TextField(verbose_name=_("Bind CN"))
|
||||
bind_password = models.TextField()
|
||||
bind_cn = models.TextField(verbose_name=_("Bind CN"), blank=True)
|
||||
bind_password = models.TextField(blank=True)
|
||||
start_tls = models.BooleanField(default=False, verbose_name=_("Enable Start TLS"))
|
||||
|
||||
base_dn = models.TextField(verbose_name=_("Base DN"))
|
||||
|
@ -64,7 +64,6 @@ class LDAPSource(Source):
|
|||
"This can only be enabled on a single LDAP source."
|
||||
)
|
||||
),
|
||||
unique=True,
|
||||
)
|
||||
sync_groups = models.BooleanField(default=True)
|
||||
sync_parent_group = models.ForeignKey(
|
||||
|
|
51
authentik/sources/ldap/tests/test_api.py
Normal file
51
authentik/sources/ldap/tests/test_api.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
"""LDAP Source API tests"""
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.providers.oauth2.generators import generate_client_secret
|
||||
from authentik.sources.ldap.api import LDAPSourceSerializer
|
||||
from authentik.sources.ldap.models import LDAPSource
|
||||
|
||||
LDAP_PASSWORD = generate_client_secret()
|
||||
|
||||
|
||||
class LDAPAPITests(APITestCase):
|
||||
"""LDAP API tests"""
|
||||
|
||||
def test_sync_users_password_valid(self):
|
||||
"""Check that single source with sync_users_password is valid"""
|
||||
serializer = LDAPSourceSerializer(
|
||||
data={
|
||||
"name": "foo",
|
||||
"slug": " foo",
|
||||
"server_uri": "ldaps://1.2.3.4",
|
||||
"bind_cn": "",
|
||||
"bind_password": LDAP_PASSWORD,
|
||||
"base_dn": "dc=foo",
|
||||
"sync_users_password": True,
|
||||
}
|
||||
)
|
||||
self.assertTrue(serializer.is_valid())
|
||||
|
||||
def test_sync_users_password_invalid(self):
|
||||
"""Ensure only a single source with password sync can be created"""
|
||||
LDAPSource.objects.create(
|
||||
name="foo",
|
||||
slug="foo",
|
||||
server_uri="ldaps://1.2.3.4",
|
||||
bind_cn="",
|
||||
bind_password=LDAP_PASSWORD,
|
||||
base_dn="dc=foo",
|
||||
sync_users_password=True,
|
||||
)
|
||||
serializer = LDAPSourceSerializer(
|
||||
data={
|
||||
"name": "foo",
|
||||
"slug": " foo",
|
||||
"server_uri": "ldaps://1.2.3.4",
|
||||
"bind_cn": "",
|
||||
"bind_password": LDAP_PASSWORD,
|
||||
"base_dn": "dc=foo",
|
||||
"sync_users_password": False,
|
||||
}
|
||||
)
|
||||
self.assertFalse(serializer.is_valid())
|
|
@ -22468,7 +22468,6 @@ components:
|
|||
description: Property mappings used for group creation/updating.
|
||||
required:
|
||||
- base_dn
|
||||
- bind_cn
|
||||
- component
|
||||
- name
|
||||
- pk
|
||||
|
@ -22565,8 +22564,6 @@ components:
|
|||
description: Property mappings used for group creation/updating.
|
||||
required:
|
||||
- base_dn
|
||||
- bind_cn
|
||||
- bind_password
|
||||
- name
|
||||
- server_uri
|
||||
- slug
|
||||
|
|
|
@ -129,21 +129,19 @@ export class LDAPSourceForm extends ModelForm<LDAPSource, string> {
|
|||
${t`To use SSL instead, use 'ldaps://' and disable this option.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Bind CN`} ?required=${true} name="bindCn">
|
||||
<ak-form-element-horizontal label=${t`Bind CN`} name="bindCn">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.bindCn)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Bind Password`}
|
||||
?required=${true}
|
||||
?writeOnly=${this.instance !== undefined}
|
||||
name="bindPassword"
|
||||
>
|
||||
<input type="text" value="" class="pf-c-form-control" required />
|
||||
<input type="text" value="" class="pf-c-form-control" />
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Base DN`} ?required=${true} name="baseDn">
|
||||
<input
|
||||
|
|
Reference in a new issue