Files
ebbe.bassandClaude Sonnet 5.5 b87ffae94c feat: read the lockout threshold and window in the password policy
The password change must stay below the domain's lockout threshold. The policy now carries lockout_threshold and lockout_window, from the domain or from the fine-grained policy that applies. AD-2's read allow-list names the new attributes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
2026-10-18 09:55:10 +02:00

80 lines
2.9 KiB
Python

"""Opt-in check against the real test OU. Skipped unless PORTAL_LIVE_LDAP=1.
Set LIVE_LDAP_SERVER, LIVE_LDAP_USER_OU_DN, LIVE_LDAP_BIND_DN, LIVE_LDAP_BIND_PASSWORD,
LIVE_LDAP_CA_FILE, LIVE_LDAP_PUBLISHER_GROUP_DN, LIVE_LDAP_ADMIN_GROUP_DN and
LIVE_LDAP_TEST_PASSWORD (the current password of ptest-user1). Needs python-ldap (Linux).
Only ever point this at the test OU. It restores what it writes and never changes a password.
"""
import os
from pathlib import Path
import pytest
from portal.adapters.directory import Forbidden, LdapDirectory
from portal.config.env import DirectoryConfig
pytestmark = pytest.mark.skipif(
os.environ.get("PORTAL_LIVE_LDAP") != "1", reason="set PORTAL_LIVE_LDAP=1 to run"
)
@pytest.fixture(scope="module")
def live():
pytest.importorskip("ldap")
env = os.environ
config = DirectoryConfig(
server=env["LIVE_LDAP_SERVER"],
user_ou_dn=env["LIVE_LDAP_USER_OU_DN"],
bind_dn=env["LIVE_LDAP_BIND_DN"],
bind_password=env["LIVE_LDAP_BIND_PASSWORD"],
ca_file=Path(env["LIVE_LDAP_CA_FILE"]),
publisher_group_dn=env["LIVE_LDAP_PUBLISHER_GROUP_DN"],
admin_group_dn=env["LIVE_LDAP_ADMIN_GROUP_DN"],
)
return LdapDirectory(config)
def test_reads_state_and_roles(live):
user = live.find_user("ptest-user1")
assert user is not None and user.enabled and not user.is_publisher
assert live.find_user("ptest-publisher").is_publisher
assert live.find_user("ptest-admin").is_admin
assert live.find_user("ptest-disabled").enabled is False
assert live.find_user("ptest-expired").must_change_password is True
assert live.find_user("does-not-exist") is None
def test_users_outside_the_ou_are_not_found(live):
assert live.find_user("Administrator") is None
def test_allowed_attributes_are_written_and_restored(live):
before = live.find_user("ptest-user1")
try:
live.update_attributes("ptest-user1", {"mobile": "+49 000 0000", "displayName": "Test X"})
after = live.find_user("ptest-user1")
assert (after.mobile, after.display_name) == ("+49 000 0000", "Test X")
finally:
live.update_attributes(
"ptest-user1", {"mobile": before.mobile, "displayName": before.display_name}
)
assert live.find_user("ptest-user1").mobile == before.mobile
def test_other_attributes_are_refused(live):
with pytest.raises(Forbidden):
live.update_attributes("ptest-user1", {"description": "x"})
def test_password_policy_is_returned(live):
policy = live.password_policy("ptest-user1")
assert policy.min_length >= 0
assert policy.lockout_threshold >= 0 # 0: the domain never locks accounts
def test_verify_password(live):
assert live.verify_password("ptest-user1", os.environ["LIVE_LDAP_TEST_PASSWORD"]) is True
assert live.verify_password("ptest-user1", "definitely-not-the-password") is False
assert live.verify_password("ptest-user1", "") is False