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

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