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

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