Repository: goauthentik/authentik

Issue:
LDAP Writeback doesn't work with eDirectory
**Describe the bug**
POST /api/v3/flows/executor/default-password-change/
LDAP Change Password writeback not working with eDirectory, looks like it's hanging on the Check Active Directory pwdProperties.

** Possible Fix **
Perhaps one could check the Root DSE for vendorVersion attribute and if it contains eDirectory as per the following value: LDAP Agent for NetIQ eDirectory 9.2.4 (40205.00) it could skip AD Password Policy Checking.
It seems eDirectory support RFC3062 for LDAP Changing password as per https://www.netiq.com/documentation/edir88/edir88/data/b1hpzdbr.html

**To Reproduce**
Steps to reproduce the behavior:
1. Setup eDirectory LDAP Sync as a Federation Source and set LDAP Password Writeback
2. Login as a LDAP Synchronised user
3. Change Password
4. Get Something went wrong! Please try again later. Error

**Expected behavior**
Change Password is expected to write back to the LDAP Server

**Logs**
Traceback (most recent call last):
  File "/authentik/flows/views/executor.py", line 340, in post
    stage_response = self.current_stage_view.post(request, *args, **kwargs)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/authentik/flows/stage.py", line 98, in post
    if not challenge.is_valid():
           ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py", line 227, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py", line 426, in run_validation
    value = self.to_internal_value(data)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/rest_framework/serializers.py", line 485, in to_internal_value
    validated_value = validate_method(validated_value)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/authentik/stages/prompt/stage.py", line 151, in password_single_clean
    password_validate.send(sender=self, password=value, plan_context=self.plan.context)
  File "/usr/local/lib/python3.11/site-packages/django/dispatch/dispatcher.py", line 176, in send
    return [
           ^
  File "/usr/local/lib/python3.11/site-packages/django/dispatch/dispatcher.py", line 177, in <listcomp>
    (receiver, receiver(signal=self, sender=sender, **named))
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/sentry_sdk/integrations/django/signals_handlers.py", line 65, in wrapper
    return receiver(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/authentik/sources/ldap/signals.py", line 51, in ldap_password_validate
    if changer.check_ad_password_complexity_enabled():
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/authentik/sources/ldap/password.py", line 77, in check_ad_password_complexity_enabled
    pwd_properties = PwdProperties(raw_pwd_properties)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/enum.py", line 717, in __call__
    return cls.__new__(cls, value)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/enum.py", line 1141, in __new__
    raise exc
  File "/usr/local/lib/python3.11/enum.py", line 1118, in __new__
    result = cls._missing_(value)
             ^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/enum.py", line 1387, in _missing_
    raise ValueError(
builtins.ValueError: [] is not a valid PwdProperties

**Version and Deployment (please complete the following information):**
 - authentik version: [2023.2.2]
 - Deployment: docker-compose using stacks on a portainer system


Discussion:

Resolving patch:
diff --git a/authentik/sources/ldap/password.py b/authentik/sources/ldap/password.py
--- a/authentik/sources/ldap/password.py
+++ b/authentik/sources/ldap/password.py
@@ -71,10 +71,13 @@ def check_ad_password_complexity_enabled(self) -> bool:
         except (LDAPAttributeError, KeyError, IndexError):
             return False
         raw_pwd_properties = root_attrs.get("attributes", {}).get("pwdProperties", None)
-        if raw_pwd_properties is None:
+        if not raw_pwd_properties:
             return False
 
-        pwd_properties = PwdProperties(raw_pwd_properties)
+        try:
+            pwd_properties = PwdProperties(raw_pwd_properties)
+        except ValueError:
+            return False
         if PwdProperties.DOMAIN_PASSWORD_COMPLEX in pwd_properties:
             return True
 


Test patch: