Blame SOURCES/0019-Issue-4788-CLI-should-support-Temporary-Password-Rul.patch

e4a41f
From 6a741b3ef50babf2ac2479437a38829204ffd438 Mon Sep 17 00:00:00 2001
e4a41f
From: tbordaz <tbordaz@redhat.com>
e4a41f
Date: Thu, 17 Jun 2021 16:22:09 +0200
e4a41f
Subject: [PATCH] Issue 4788 - CLI should support Temporary Password Rules
e4a41f
 attributes (#4793)
e4a41f
e4a41f
Bug description:
e4a41f
    Since #4725, password policy support temporary password rules.
e4a41f
    CLI (dsconf) does not support this RFE and only direct ldap
e4a41f
    operation can configure global/local password policy
e4a41f
e4a41f
Fix description:
e4a41f
    Update dsconf to support this new RFE.
e4a41f
    To run successfully the testcase it relies on #4788
e4a41f
e4a41f
relates: #4788
e4a41f
e4a41f
Reviewed by: Simon Pichugin (thanks !!)
e4a41f
e4a41f
Platforms tested: F34
e4a41f
---
e4a41f
 .../password/pwdPolicy_attribute_test.py      | 172 ++++++++++++++++--
e4a41f
 src/lib389/lib389/cli_conf/pwpolicy.py        |   5 +-
e4a41f
 src/lib389/lib389/pwpolicy.py                 |   5 +-
e4a41f
 3 files changed, 165 insertions(+), 17 deletions(-)
e4a41f
e4a41f
diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
e4a41f
index aee3a91ad..085d0a373 100644
e4a41f
--- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
e4a41f
+++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py
e4a41f
@@ -34,7 +34,7 @@ log = logging.getLogger(__name__)
e4a41f
 
e4a41f
 
e4a41f
 @pytest.fixture(scope="module")
e4a41f
-def create_user(topology_st, request):
e4a41f
+def test_user(topology_st, request):
e4a41f
     """User for binding operation"""
e4a41f
     topology_st.standalone.config.set('nsslapd-auditlog-logging-enabled', 'on')
e4a41f
     log.info('Adding test user {}')
e4a41f
@@ -56,10 +56,11 @@ def create_user(topology_st, request):
e4a41f
         topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
+    return user
e4a41f
 
e4a41f
 
e4a41f
 @pytest.fixture(scope="module")
e4a41f
-def password_policy(topology_st, create_user):
e4a41f
+def password_policy(topology_st, test_user):
e4a41f
     """Set up password policy for subtree and user"""
e4a41f
 
e4a41f
     pwp = PwPolicyManager(topology_st.standalone)
e4a41f
@@ -71,7 +72,7 @@ def password_policy(topology_st, create_user):
e4a41f
     pwp.create_user_policy(TEST_USER_DN, policy_props)
e4a41f
 
e4a41f
 @pytest.mark.skipif(ds_is_older('1.4.3.3'), reason="Not implemented")
e4a41f
-def test_pwd_reset(topology_st, create_user):
e4a41f
+def test_pwd_reset(topology_st, test_user):
e4a41f
     """Test new password policy attribute "pwdReset"
e4a41f
 
e4a41f
     :id: 03db357b-4800-411e-a36e-28a534293004
e4a41f
@@ -124,7 +125,7 @@ def test_pwd_reset(topology_st, create_user):
e4a41f
                          [('on', 'off', ldap.UNWILLING_TO_PERFORM),
e4a41f
                           ('off', 'off', ldap.UNWILLING_TO_PERFORM),
e4a41f
                           ('off', 'on', False), ('on', 'on', False)])
e4a41f
-def test_change_pwd(topology_st, create_user, password_policy,
e4a41f
+def test_change_pwd(topology_st, test_user, password_policy,
e4a41f
                     subtree_pwchange, user_pwchange, exception):
e4a41f
     """Verify that 'passwordChange' attr works as expected
e4a41f
     User should have a priority over a subtree.
e4a41f
@@ -184,7 +185,7 @@ def test_change_pwd(topology_st, create_user, password_policy,
e4a41f
         user.reset_password(TEST_USER_PWD)
e4a41f
 
e4a41f
 
e4a41f
-def test_pwd_min_age(topology_st, create_user, password_policy):
e4a41f
+def test_pwd_min_age(topology_st, test_user, password_policy):
e4a41f
     """If we set passwordMinAge to some value, for example to 10, then it
e4a41f
     should not allow the user to change the password within 10 seconds after
e4a41f
     his previous change.
e4a41f
@@ -257,7 +258,7 @@ def test_pwd_min_age(topology_st, create_user, password_policy):
e4a41f
         topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
e4a41f
         user.reset_password(TEST_USER_PWD)
e4a41f
 
e4a41f
-def test_global_tpr_maxuse_1(topology_st, create_user, request):
e4a41f
+def test_global_tpr_maxuse_1(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRMaxUse
e4a41f
     Test that after passwordTPRMaxUse failures to bind
e4a41f
     additional bind with valid password are failing with CONSTRAINT_VIOLATION
e4a41f
@@ -374,7 +375,7 @@ def test_global_tpr_maxuse_1(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_maxuse_2(topology_st, create_user, request):
e4a41f
+def test_global_tpr_maxuse_2(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRMaxUse
e4a41f
     Test that after less than passwordTPRMaxUse failures to bind
e4a41f
     additional bind with valid password are successfull
e4a41f
@@ -474,7 +475,7 @@ def test_global_tpr_maxuse_2(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_maxuse_3(topology_st, create_user, request):
e4a41f
+def test_global_tpr_maxuse_3(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRMaxUse
e4a41f
     Test that after less than passwordTPRMaxUse failures to bind
e4a41f
     A bind with valid password is successfull but passwordMustChange
e4a41f
@@ -587,7 +588,7 @@ def test_global_tpr_maxuse_3(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_maxuse_4(topology_st, create_user, request):
e4a41f
+def test_global_tpr_maxuse_4(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRMaxUse
e4a41f
     Test that a TPR attribute passwordTPRMaxUse
e4a41f
     can be updated by DM but not the by user itself
e4a41f
@@ -701,7 +702,148 @@ def test_global_tpr_maxuse_4(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_delayValidFrom_1(topology_st, create_user, request):
e4a41f
+def test_local_tpr_maxuse_5(topology_st, test_user, request):
e4a41f
+    """Test TPR local policy overpass global one: passwordTPRMaxUse
e4a41f
+    Test that after passwordTPRMaxUse failures to bind
e4a41f
+    additional bind with valid password are failing with CONSTRAINT_VIOLATION
e4a41f
+
e4a41f
+    :id: c3919707-d804-445a-8754-8385b1072c42
e4a41f
+    :customerscenario: False
e4a41f
+    :setup: Standalone instance
e4a41f
+    :steps:
e4a41f
+        1. Global password policy Enable passwordMustChange
e4a41f
+        2. Global password policy Set passwordTPRMaxUse=5
e4a41f
+        3. Global password policy Set passwordMaxFailure to a higher value to not disturb the test
e4a41f
+        4. Local password policy Enable passwordMustChange
e4a41f
+        5. Local password policy Set passwordTPRMaxUse=10 (higher than global)
e4a41f
+        6. Bind with a wrong password 10 times and check INVALID_CREDENTIALS
e4a41f
+        7. Check that passwordTPRUseCount got to the limit (5)
e4a41f
+        8. Bind with a wrong password (CONSTRAINT_VIOLATION)
e4a41f
+           and check passwordTPRUseCount overpass the limit by 1 (11)
e4a41f
+        9. Bind with a valid password 10 times and check CONSTRAINT_VIOLATION
e4a41f
+           and check passwordTPRUseCount increases
e4a41f
+        10. Reset password policy configuration and remove local password from user
e4a41f
+    :expected results:
e4a41f
+        1. Success
e4a41f
+        2. Success
e4a41f
+        3. Success
e4a41f
+        4. Success
e4a41f
+        5. Success
e4a41f
+        6. Success
e4a41f
+        7. Success
e4a41f
+        8. Success
e4a41f
+        9. Success
e4a41f
+        10. Success
e4a41f
+    """
e4a41f
+
e4a41f
+    global_tpr_maxuse = 5
e4a41f
+    # Set global password policy config, passwordMaxFailure being higher than
e4a41f
+    # passwordTPRMaxUse so that TPR is enforced first
e4a41f
+    topology_st.standalone.config.replace('passwordMustChange', 'on')
e4a41f
+    topology_st.standalone.config.replace('passwordMaxFailure', str(global_tpr_maxuse + 20))
e4a41f
+    topology_st.standalone.config.replace('passwordTPRMaxUse', str(global_tpr_maxuse))
e4a41f
+    time.sleep(.5)
e4a41f
+
e4a41f
+    local_tpr_maxuse = global_tpr_maxuse + 5
e4a41f
+    # Reset user's password with a local password policy
e4a41f
+    # that has passwordTPRMaxUse higher than global
e4a41f
+    #our_user = UserAccount(topology_st.standalone, TEST_USER_DN)
e4a41f
+    subprocess.call(['%s/dsconf' % topology_st.standalone.get_sbin_dir(),
e4a41f
+                     'slapd-standalone1',
e4a41f
+                     'localpwp',
e4a41f
+                     'adduser',
e4a41f
+                     test_user.dn])
e4a41f
+    subprocess.call(['%s/dsconf' % topology_st.standalone.get_sbin_dir(),
e4a41f
+                     'slapd-standalone1',
e4a41f
+                     'localpwp',
e4a41f
+                     'set',
e4a41f
+                     '--pwptprmaxuse',
e4a41f
+                     str(local_tpr_maxuse),
e4a41f
+                     '--pwdmustchange',
e4a41f
+                     'on',
e4a41f
+                     test_user.dn])
e4a41f
+    test_user.replace('userpassword', PASSWORD)
e4a41f
+    time.sleep(.5)
e4a41f
+
e4a41f
+    # look up to passwordTPRMaxUse with failing
e4a41f
+    # bind to check that the limits of TPR are enforced
e4a41f
+    for i in range(local_tpr_maxuse):
e4a41f
+        # Bind as user with a wrong password
e4a41f
+        with pytest.raises(ldap.INVALID_CREDENTIALS):
e4a41f
+            test_user.rebind('wrong password')
e4a41f
+        time.sleep(.5)
e4a41f
+
e4a41f
+        # Check that pwdReset is TRUE
e4a41f
+        topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
e4a41f
+        #assert test_user.get_attr_val_utf8('pwdReset') == 'TRUE'
e4a41f
+
e4a41f
+        # Check that pwdTPRReset is TRUE
e4a41f
+        assert test_user.get_attr_val_utf8('pwdTPRReset') == 'TRUE'
e4a41f
+        assert test_user.get_attr_val_utf8('pwdTPRUseCount') == str(i+1)
e4a41f
+        log.info("%dth failing bind (INVALID_CREDENTIALS) => pwdTPRUseCount = %d" % (i+1, i+1))
e4a41f
+
e4a41f
+
e4a41f
+    # Now the #failures reached passwordTPRMaxUse
e4a41f
+    # Check that pwdReset is TRUE
e4a41f
+    topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
e4a41f
+
e4a41f
+    # Check that pwdTPRReset is TRUE
e4a41f
+    assert test_user.get_attr_val_utf8('pwdTPRReset') == 'TRUE'
e4a41f
+    assert test_user.get_attr_val_utf8('pwdTPRUseCount') == str(local_tpr_maxuse)
e4a41f
+    log.info("last failing bind (INVALID_CREDENTIALS) => pwdTPRUseCount = %d" % (local_tpr_maxuse))
e4a41f
+
e4a41f
+    # Bind as user with wrong password --> ldap.CONSTRAINT_VIOLATION
e4a41f
+    with pytest.raises(ldap.CONSTRAINT_VIOLATION):
e4a41f
+        test_user.rebind("wrong password")
e4a41f
+    time.sleep(.5)
e4a41f
+
e4a41f
+    # Check that pwdReset is TRUE
e4a41f
+    topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
e4a41f
+
e4a41f
+    # Check that pwdTPRReset is TRUE
e4a41f
+    assert test_user.get_attr_val_utf8('pwdTPRReset') == 'TRUE'
e4a41f
+    assert test_user.get_attr_val_utf8('pwdTPRUseCount') == str(local_tpr_maxuse + 1)
e4a41f
+    log.info("failing bind (CONSTRAINT_VIOLATION) => pwdTPRUseCount = %d" % (local_tpr_maxuse + i))
e4a41f
+
e4a41f
+    # Now check that all next attempts with correct password are all in LDAP_CONSTRAINT_VIOLATION
e4a41f
+    # and passwordTPRRetryCount remains unchanged
e4a41f
+    # account is now similar to locked
e4a41f
+    for i in range(10):
e4a41f
+        # Bind as user with valid password
e4a41f
+        with pytest.raises(ldap.CONSTRAINT_VIOLATION):
e4a41f
+            test_user.rebind(PASSWORD)
e4a41f
+        time.sleep(.5)
e4a41f
+
e4a41f
+        # Check that pwdReset is TRUE
e4a41f
+        topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
e4a41f
+
e4a41f
+        # Check that pwdTPRReset is TRUE
e4a41f
+        # pwdTPRUseCount keeps increasing
e4a41f
+        assert test_user.get_attr_val_utf8('pwdTPRReset') == 'TRUE'
e4a41f
+        assert test_user.get_attr_val_utf8('pwdTPRUseCount') == str(local_tpr_maxuse + i + 2)
e4a41f
+        log.info("Rejected bind (CONSTRAINT_VIOLATION) => pwdTPRUseCount = %d" % (local_tpr_maxuse + i + 2))
e4a41f
+
e4a41f
+
e4a41f
+    def fin():
e4a41f
+        topology_st.standalone.restart()
e4a41f
+        # Reset password policy config
e4a41f
+        topology_st.standalone.simple_bind_s(DN_DM, PASSWORD)
e4a41f
+        topology_st.standalone.config.replace('passwordMustChange', 'off')
e4a41f
+
e4a41f
+        # Remove local password policy from that entry
e4a41f
+        subprocess.call(['%s/dsconf' % topology_st.standalone.get_sbin_dir(),
e4a41f
+                        'slapd-standalone1',
e4a41f
+                        'localpwp',
e4a41f
+                        'remove',
e4a41f
+                        test_user.dn])
e4a41f
+
e4a41f
+        # Reset user's password
e4a41f
+        test_user.replace('userpassword', TEST_USER_PWD)
e4a41f
+
e4a41f
+
e4a41f
+    request.addfinalizer(fin)
e4a41f
+
e4a41f
+def test_global_tpr_delayValidFrom_1(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRDelayValidFrom
e4a41f
     Test that a TPR password is not valid before reset time +
e4a41f
     passwordTPRDelayValidFrom
e4a41f
@@ -766,7 +908,7 @@ def test_global_tpr_delayValidFrom_1(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_delayValidFrom_2(topology_st, create_user, request):
e4a41f
+def test_global_tpr_delayValidFrom_2(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRDelayValidFrom
e4a41f
     Test that a TPR password is valid after reset time +
e4a41f
     passwordTPRDelayValidFrom
e4a41f
@@ -838,7 +980,7 @@ def test_global_tpr_delayValidFrom_2(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_delayValidFrom_3(topology_st, create_user, request):
e4a41f
+def test_global_tpr_delayValidFrom_3(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRDelayValidFrom
e4a41f
     Test that a TPR attribute passwordTPRDelayValidFrom
e4a41f
     can be updated by DM but not the by user itself
e4a41f
@@ -940,7 +1082,7 @@ def test_global_tpr_delayValidFrom_3(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_delayExpireAt_1(topology_st, create_user, request):
e4a41f
+def test_global_tpr_delayExpireAt_1(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRDelayExpireAt
e4a41f
     Test that a TPR password is not valid after reset time +
e4a41f
     passwordTPRDelayExpireAt
e4a41f
@@ -1010,7 +1152,7 @@ def test_global_tpr_delayExpireAt_1(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_delayExpireAt_2(topology_st, create_user, request):
e4a41f
+def test_global_tpr_delayExpireAt_2(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRDelayExpireAt
e4a41f
     Test that a TPR password is valid before reset time +
e4a41f
     passwordTPRDelayExpireAt
e4a41f
@@ -1082,7 +1224,7 @@ def test_global_tpr_delayExpireAt_2(topology_st, create_user, request):
e4a41f
 
e4a41f
     request.addfinalizer(fin)
e4a41f
 
e4a41f
-def test_global_tpr_delayExpireAt_3(topology_st, create_user, request):
e4a41f
+def test_global_tpr_delayExpireAt_3(topology_st, test_user, request):
e4a41f
     """Test global TPR policy : passwordTPRDelayExpireAt
e4a41f
     Test that a TPR attribute passwordTPRDelayExpireAt
e4a41f
     can be updated by DM but not the by user itself
e4a41f
diff --git a/src/lib389/lib389/cli_conf/pwpolicy.py b/src/lib389/lib389/cli_conf/pwpolicy.py
e4a41f
index 2838afcb8..26af6e7ec 100644
e4a41f
--- a/src/lib389/lib389/cli_conf/pwpolicy.py
e4a41f
+++ b/src/lib389/lib389/cli_conf/pwpolicy.py
e4a41f
@@ -255,6 +255,9 @@ def create_parser(subparsers):
e4a41f
     set_parser.add_argument('--pwpinheritglobal', help="Set to \"on\" to allow local policies to inherit the global policy")
e4a41f
     set_parser.add_argument('--pwddictcheck', help="Set to \"on\" to enforce CrackLib dictionary checking")
e4a41f
     set_parser.add_argument('--pwddictpath', help="Filesystem path to specific/custom CrackLib dictionary files")
e4a41f
+    set_parser.add_argument('--pwptprmaxuse', help="Number of times a reset password can be used for authentication")
e4a41f
+    set_parser.add_argument('--pwptprdelayexpireat', help="Number of seconds after which a reset password expires")
e4a41f
+    set_parser.add_argument('--pwptprdelayvalidfrom', help="Number of seconds to wait before using a reset password to authenticated")
e4a41f
     # delete local password policy
e4a41f
     del_parser = local_subcommands.add_parser('remove', help='Remove a local password policy')
e4a41f
     del_parser.set_defaults(func=del_local_policy)
e4a41f
@@ -291,4 +294,4 @@ def create_parser(subparsers):
e4a41f
     #############################################
e4a41f
     set_parser.add_argument('DN', nargs=1, help='Set the local policy for this entry DN')
e4a41f
     add_subtree_parser.add_argument('DN', nargs=1, help='Add/replace the subtree policy for this entry DN')
e4a41f
-    add_user_parser.add_argument('DN', nargs=1, help='Add/replace the local password policy for this entry DN')
e4a41f
\ No newline at end of file
e4a41f
+    add_user_parser.add_argument('DN', nargs=1, help='Add/replace the local password policy for this entry DN')
e4a41f
diff --git a/src/lib389/lib389/pwpolicy.py b/src/lib389/lib389/pwpolicy.py
e4a41f
index 8653cb195..d2427933b 100644
e4a41f
--- a/src/lib389/lib389/pwpolicy.py
e4a41f
+++ b/src/lib389/lib389/pwpolicy.py
e4a41f
@@ -65,7 +65,10 @@ class PwPolicyManager(object):
e4a41f
             'pwddictcheck': 'passworddictcheck',
e4a41f
             'pwddictpath': 'passworddictpath',
e4a41f
             'pwdallowhash': 'nsslapd-allow-hashed-passwords',
e4a41f
-            'pwpinheritglobal': 'nsslapd-pwpolicy-inherit-global'
e4a41f
+            'pwpinheritglobal': 'nsslapd-pwpolicy-inherit-global',
e4a41f
+            'pwptprmaxuse': 'passwordTPRMaxUse',
e4a41f
+            'pwptprdelayexpireat': 'passwordTPRDelayExpireAt',
e4a41f
+            'pwptprdelayvalidfrom': 'passwordTPRDelayValidFrom'
e4a41f
         }
e4a41f
 
e4a41f
     def is_subtree_policy(self, dn):
e4a41f
-- 
e4a41f
2.31.1
e4a41f