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