diff --git a/authentik/sources/ldap/api.py b/authentik/sources/ldap/api.py index dc45523380..8a672a6aa7 100644 --- a/authentik/sources/ldap/api.py +++ b/authentik/sources/ldap/api.py @@ -77,6 +77,7 @@ class LDAPSourceSerializer(SourceSerializer): "group_object_filter", "group_membership_field", "object_uniqueness_field", + "password_login_update_internal_password", "sync_users", "sync_users_password", "sync_groups", @@ -118,6 +119,7 @@ class LDAPSourceViewSet(UsedByMixin, ModelViewSet): "group_object_filter", "group_membership_field", "object_uniqueness_field", + "password_login_update_internal_password", "sync_users", "sync_users_password", "sync_groups", diff --git a/authentik/sources/ldap/auth.py b/authentik/sources/ldap/auth.py index a271dac5a5..37321ea05f 100644 --- a/authentik/sources/ldap/auth.py +++ b/authentik/sources/ldap/auth.py @@ -41,10 +41,11 @@ class LDAPBackend(InbuiltBackend): # or has a password, but couldn't be authenticated by ModelBackend. # This means we check with a bind to see if the LDAP password has changed if self.auth_user_by_bind(source, user, password): - # Password given successfully binds to LDAP, so we save it in our Database - LOGGER.debug("Updating user's password in DB", user=user) - user.set_password(password, signal=False) - user.save() + if source.password_login_update_internal_password: + # Password given successfully binds to LDAP, so we save it in our Database + LOGGER.debug("Updating user's password in DB", user=user) + user.set_password(password, signal=False) + user.save() return user # Password doesn't match LOGGER.debug("Failed to bind, password invalid") diff --git a/authentik/sources/ldap/migrations/0004_ldapsource_password_login_update_internal_password.py b/authentik/sources/ldap/migrations/0004_ldapsource_password_login_update_internal_password.py new file mode 100644 index 0000000000..cacbe62684 --- /dev/null +++ b/authentik/sources/ldap/migrations/0004_ldapsource_password_login_update_internal_password.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.1 on 2024-01-31 18:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authentik_sources_ldap", "0003_ldapsource_client_certificate_ldapsource_sni_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="ldapsource", + name="password_login_update_internal_password", + field=models.BooleanField( + default=True, + help_text="Update internal authentik password when login succeeds with LDAP", + ), + ), + migrations.AlterField( + model_name="ldapsource", + name="password_login_update_internal_password", + field=models.BooleanField( + default=False, + help_text="Update internal authentik password when login succeeds with LDAP", + ), + ), + ] diff --git a/authentik/sources/ldap/models.py b/authentik/sources/ldap/models.py index 074e49b5f4..06ced2f9fa 100644 --- a/authentik/sources/ldap/models.py +++ b/authentik/sources/ldap/models.py @@ -98,6 +98,11 @@ class LDAPSource(Source): help_text=_("Property mappings used for group creation/updating."), ) + password_login_update_internal_password = models.BooleanField( + default=False, + help_text=_("Update internal authentik password when login succeeds with LDAP"), + ) + sync_users = models.BooleanField(default=True) sync_users_password = models.BooleanField( default=True, diff --git a/blueprints/schema.json b/blueprints/schema.json index da1082c7aa..942f37a039 100644 --- a/blueprints/schema.json +++ b/blueprints/schema.json @@ -4347,6 +4347,11 @@ "title": "Object uniqueness field", "description": "Field which contains a unique Identifier." }, + "password_login_update_internal_password": { + "type": "boolean", + "title": "Password login update internal password", + "description": "Update internal authentik password when login succeeds with LDAP" + }, "sync_users": { "type": "boolean", "title": "Sync users" diff --git a/schema.yml b/schema.yml index fd10552093..a14f542873 100644 --- a/schema.yml +++ b/schema.yml @@ -19926,6 +19926,10 @@ paths: description: Number of results to return per page. schema: type: integer + - in: query + name: password_login_update_internal_password + schema: + type: boolean - in: query name: peer_certificate schema: @@ -35225,6 +35229,10 @@ components: object_uniqueness_field: type: string description: Field which contains a unique Identifier. + password_login_update_internal_password: + type: boolean + description: Update internal authentik password when login succeeds with + LDAP sync_users: type: boolean sync_users_password: @@ -35366,6 +35374,10 @@ components: type: string minLength: 1 description: Field which contains a unique Identifier. + password_login_update_internal_password: + type: boolean + description: Update internal authentik password when login succeeds with + LDAP sync_users: type: boolean sync_users_password: @@ -39440,6 +39452,10 @@ components: type: string minLength: 1 description: Field which contains a unique Identifier. + password_login_update_internal_password: + type: boolean + description: Update internal authentik password when login succeeds with + LDAP sync_users: type: boolean sync_users_password: diff --git a/tests/e2e/test_source_ldap_samba.py b/tests/e2e/test_source_ldap_samba.py index d5210f6b51..fb9aa6d12a 100644 --- a/tests/e2e/test_source_ldap_samba.py +++ b/tests/e2e/test_source_ldap_samba.py @@ -128,6 +128,7 @@ class TestSourceLDAPSamba(SeleniumTestCase): base_dn="dc=test,dc=goauthentik,dc=io", additional_user_dn="ou=users", additional_group_dn="ou=groups", + password_login_update_internal_password=True, ) source.property_mappings.set( LDAPPropertyMapping.objects.filter( diff --git a/web/src/admin/sources/ldap/LDAPSourceForm.ts b/web/src/admin/sources/ldap/LDAPSourceForm.ts index f62478963c..a5b9d41c9c 100644 --- a/web/src/admin/sources/ldap/LDAPSourceForm.ts +++ b/web/src/admin/sources/ldap/LDAPSourceForm.ts @@ -86,6 +86,28 @@ export class LDAPSourceForm extends BaseSourceForm { ${msg("Enabled")} + + +

+ ${msg( + "When the user logs in to authentik using this source password backend, update their credentials in authentik.", + )} +

+