From 7923698d9fee944871037fc87549842ad6d832d7 Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Feb 27 2020 14:16:26 +0000 Subject: Issue 50912 - RFE - add password policy attribute pwdReset Description: Implement the Password Policy attribute "pwdReset" as described at: https://tools.ietf.org/html/draft-behera-ldap-password-policy-10 relates: https://pagure.io/389-ds-base/issue/50912 Reviewed by: firstyear & tbordaz(Thanks!) --- diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py index 02e0922..b37eff7 100644 --- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py +++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py @@ -11,7 +11,7 @@ from lib389.tasks import * from lib389.utils import * from lib389.topologies import topology_st from lib389.pwpolicy import PwPolicyManager -from lib389.idm.user import UserAccounts, TEST_USER_PROPERTIES +from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES from lib389.idm.organizationalunit import OrganizationalUnits from lib389._constants import (DEFAULT_SUFFIX, DN_DM, PASSWORD) @@ -70,6 +70,51 @@ 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): + """Test new password policy attribute "pwdReset" + + :id: 03db357b-4800-411e-a36e-28a534293004 + :setup: Standalone instance + :steps: + 1. Enable passwordMustChange + 2. Reset user's password + 3. Check that the pwdReset attribute is set to TRUE + 4. Bind as the user and change its password + 5. Check that pwdReset is now set to FALSE + 6. Reset password policy configuration + :expected results: + 1. Success + 2. Success + 3. Success + 4. Success + 5. Success + 6. Success + """ + + # Set password policy config + topology_st.standalone.config.replace('passwordMustChange', 'on') + time.sleep(.5) + + # Reset user's password + our_user = UserAccount(topology_st.standalone, TEST_USER_DN) + our_user.replace('userpassword', PASSWORD) + + # Check that pwdReset is TRUE + assert our_user.get_attr_val_utf8('pwdReset') == 'TRUE' + + # Bind as user and change its own password + our_user.rebind(PASSWORD) + our_user.replace('userpassword', PASSWORD) + + # Check that pwdReset is FALSE + topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) + assert our_user.get_attr_val_utf8('pwdReset') == 'FALSE' + + # Reset password policy config + topology_st.standalone.config.replace('passwordMustChange', 'off') + + @pytest.mark.parametrize('subtree_pwchange,user_pwchange,exception', [('on', 'off', ldap.UNWILLING_TO_PERFORM), ('off', 'off', ldap.UNWILLING_TO_PERFORM), @@ -114,7 +159,6 @@ def test_change_pwd(topology_st, create_user, password_policy, user_policy.set('passwordChange', user_pwchange) user_policy.set('passwordExp', 'on') - print("MARK attach gdb") time.sleep(1) try: diff --git a/ldap/schema/02common.ldif b/ldap/schema/02common.ldif index 23d38f7..966636b 100644 --- a/ldap/schema/02common.ldif +++ b/ldap/schema/02common.ldif @@ -76,6 +76,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2349 NAME ( 'passwordDictCheck' 'pwdDict attributeTypes: ( 2.16.840.1.113730.3.1.2350 NAME ( 'passwordDictPath' 'pwdDictPath' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.2351 NAME ( 'passwordUserAttributes' 'pwdUserAttributes' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.2352 NAME ( 'passwordBadWords' 'pwdBadWords' ) DESC '389 Directory Server password policy attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2366 NAME 'pwdReset' DESC '389 Directory Server password policy attribute type' EQUALITY booleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE USAGE directoryOperation X-ORIGIN '389 Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.198 NAME 'memberURL' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.199 NAME 'memberCertificateDescription' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.207 NAME 'vlvBase' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'Netscape Directory Server' ) @@ -155,7 +156,7 @@ objectClasses: ( 2.16.840.1.113730.3.2.10 NAME 'netscapeServer' DESC 'Netscape d objectClasses: ( 2.16.840.1.113730.3.2.7 NAME 'nsLicenseUser' DESC 'Netscape defined objectclass' SUP top MAY ( nsLicensedFor $ nsLicenseStartTime $ nsLicenseEndTime ) X-ORIGIN 'Netscape Administration Services' ) objectClasses: ( 2.16.840.1.113730.3.2.1 NAME 'changeLogEntry' DESC 'LDAP changelog objectclass' SUP top MUST ( targetdn $ changeTime $ changenumber $ changeType ) MAY ( changes $ newrdn $ deleteoldrdn $ newsuperior ) X-ORIGIN 'Changelog Internet Draft' ) objectClasses: ( 2.16.840.1.113730.3.2.6 NAME 'referral' DESC 'LDAP referrals objectclass' SUP top MAY ( ref ) X-ORIGIN 'LDAPv3 referrals Internet Draft' ) -objectClasses: ( 2.16.840.1.113730.3.2.12 NAME 'passwordObject' DESC 'Netscape defined password policy objectclass' SUP top MAY ( pwdpolicysubentry $ passwordExpirationTime $ passwordExpWarned $ passwordRetryCount $ retryCountResetTime $ accountUnlockTime $ passwordHistory $ passwordAllowChangeTime $ passwordGraceUserTime ) X-ORIGIN 'Netscape Directory Server' ) +objectClasses: ( 2.16.840.1.113730.3.2.12 NAME 'passwordObject' DESC 'Netscape defined password policy objectclass' SUP top MAY ( pwdpolicysubentry $ passwordExpirationTime $ passwordExpWarned $ passwordRetryCount $ retryCountResetTime $ accountUnlockTime $ passwordHistory $ passwordAllowChangeTime $ passwordGraceUserTime $ pwdReset ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.13 NAME 'passwordPolicy' DESC 'Netscape defined password policy objectclass' SUP top MAY ( passwordMaxAge $ passwordExp $ passwordMinLength $ passwordKeepHistory $ passwordInHistory $ passwordChange $ passwordWarning $ passwordLockout $ passwordMaxFailure $ passwordResetDuration $ passwordUnlock $ passwordLockoutDuration $ passwordCheckSyntax $ passwordMustChange $ passwordStorageScheme $ passwordMinAge $ passwordResetFailureCount $ passwordGraceLimit $ passwordMinDigits $ passwordMinAlphas $ passwordMinUppers $ passwordMinLowers $ passwordMinSpecials $ passwordMin8bit $ passwordMaxRepeats $ passwordMinCategories $ passwordMinTokenLength $ passwordTrackUpdateTime $ passwordAdminDN $ passwordDictCheck $ passwordDictPath $ passwordPalindrome $ passwordMaxSequence $ passwordMaxClassChars $ passwordMaxSeqSets $ passwordBadWords $ passwordUserAttributes $ passwordSendExpiringTime ) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.30 NAME 'glue' DESC 'Netscape defined objectclass' SUP top X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.32 NAME 'netscapeMachineData' DESC 'Netscape defined objectclass' SUP top X-ORIGIN 'Netscape Directory Server' ) diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c index 2e11caa..7d67a67 100644 --- a/ldap/servers/slapd/pw.c +++ b/ldap/servers/slapd/pw.c @@ -715,6 +715,14 @@ update_pw_info(Slapi_PBlock *pb, char *old_pw) slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "passwordgraceusertime", "0"); } + if (slapi_entry_attr_hasvalue(e, "pwdReset", "TRUE")) { + /* + * Password was previously reset, just reset the "reset" flag for now. + * If the password is being reset again we will catch it below... + */ + slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "pwdReset", "FALSE"); + } + /* * If the password is reset by a different user, mark it the first time logon. If this is an internal * operation, we have a special case for the password modify extended operation where @@ -723,8 +731,10 @@ update_pw_info(Slapi_PBlock *pb, char *old_pw) */ if ((internal_op && pwpolicy->pw_must_change && (!pb_conn || strcasecmp(target_dn, pb_conn->c_dn))) || (!internal_op && pwpolicy->pw_must_change && - ((target_dn && bind_dn && strcasecmp(target_dn, bind_dn)) && pw_is_pwp_admin(pb, pwpolicy)))) { + ((target_dn && bind_dn && strcasecmp(target_dn, bind_dn)) && pw_is_pwp_admin(pb, pwpolicy)))) + { pw_exp_date = NO_TIME; + slapi_mods_add_string(&smods, LDAP_MOD_REPLACE, "pwdReset", "TRUE"); } else if (pwpolicy->pw_exp == 1) { Slapi_Entry *pse = NULL;