andykimpe / rpms / 389-ds-base

Forked from rpms/389-ds-base 5 months ago
Clone
Blob Blame History Raw
From c7f195fdf33ae995c12ff43f5b9c03da3d25b8da Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Tue, 3 May 2016 09:57:36 -0400
Subject: [PATCH 381/382] Ticket 48813 - password history is not updated when
 an admin resets the password

Bug Description:  When an admin resets a password the current password is not
                  stored in the password history. This incorrectly allows the
                  user to reuse the previous password after the reset.

Fix Description:  When a password is being reset by an "admin", still grab the
                  old password so we can correctly update the password history.

https://fedorahosted.org/389/ticket/48813

Reviewed by: nhosoi(Thanks!)

(cherry picked from commit 9c310b09c481a32b1a012371c688c65156b33472)
(cherry picked from commit b357e443d06f32bcad2f410868299d43153dca62)
(cherry picked from commit 0a5047043051701d5b361500f605d782a2befa1d)
---
 .../tests/suites/password/pwp_history_test.py      | 264 +++++++++++++++++++++
 ldap/servers/slapd/modify.c                        |   4 +
 ldap/servers/slapd/proto-slap.h                    |   1 +
 ldap/servers/slapd/pw.c                            |  29 +++
 4 files changed, 298 insertions(+)
 create mode 100644 dirsrvtests/tests/suites/password/pwp_history_test.py

diff --git a/dirsrvtests/tests/suites/password/pwp_history_test.py b/dirsrvtests/tests/suites/password/pwp_history_test.py
new file mode 100644
index 0000000..3f66efd
--- /dev/null
+++ b/dirsrvtests/tests/suites/password/pwp_history_test.py
@@ -0,0 +1,264 @@
+import os
+import ldap
+import logging
+import pytest
+from lib389 import DirSrv, Entry
+from lib389._constants import *
+from lib389.properties import *
+from lib389.tasks import *
+from lib389.utils import *
+
+logging.getLogger(__name__).setLevel(logging.DEBUG)
+log = logging.getLogger(__name__)
+
+
+class TopologyStandalone(object):
+    """ Topology class """
+    def __init__(self, standalone):
+        """ init """
+        standalone.open()
+        self.standalone = standalone
+
+
+@pytest.fixture(scope="module")
+def topology(request):
+    """
+    Creating standalone instance ...
+    """
+    standalone = DirSrv(verbose=False)
+    args_instance[SER_HOST] = HOST_STANDALONE
+    args_instance[SER_PORT] = PORT_STANDALONE
+    args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
+    args_instance[SER_CREATION_SUFFIX] = DEFAULT_SUFFIX
+    args_standalone = args_instance.copy()
+    standalone.allocate(args_standalone)
+    instance_standalone = standalone.exists()
+    if instance_standalone:
+        standalone.delete()
+    standalone.create()
+    standalone.open()
+
+    # Delete each instance in the end
+    def fin():
+        """ Clean up instance """
+        standalone.delete()
+    request.addfinalizer(fin)
+
+    # Clear out the tmp dir
+    standalone.clearTmpDir(__file__)
+
+    return TopologyStandalone(standalone)
+
+
+def test_pwp_history_test(topology):
+    """
+    Test password policy history feature:
+        - Test password history is enforced
+        - Test password history works after an Admin resets the password
+        - Test that the correct number of passwords are stored in history
+    """
+
+    USER_DN = 'uid=testuser,' + DEFAULT_SUFFIX
+
+    #
+    # Configure password history policy and add a test user
+    #
+    try:
+        topology.standalone.modify_s("cn=config",
+                                     [(ldap.MOD_REPLACE,
+                                       'passwordHistory', 'on'),
+                                      (ldap.MOD_REPLACE,
+                                       'passwordInHistory', '3'),
+                                      (ldap.MOD_REPLACE,
+                                       'passwordChange', 'on'),
+                                      (ldap.MOD_REPLACE,
+                                       'passwordStorageScheme', 'CLEAR')])
+        log.info('Configured password policy.')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to configure password policy: ' + str(e))
+        assert False
+
+    try:
+        topology.standalone.add_s(Entry((USER_DN, {
+                                  'objectclass': ['top', 'extensibleObject'],
+                                  'sn': 'user',
+                                  'cn': 'test user',
+                                  'uid': 'testuser',
+                                  'userpassword': 'password'})))
+    except ldap.LDAPError as e:
+        log.fatal('Failed to add test user' + USER_DN + ': error ' + str(e))
+        assert False
+
+    #
+    # Test that password history is enforced.
+    #
+    try:
+        topology.standalone.simple_bind_s(USER_DN, 'password')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind as user: ' + str(e))
+        assert False
+
+    # Attempt to change password to the same password
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password')])
+        log.info('Incorrectly able to to set password to existing password.')
+        assert False
+    except ldap.CONSTRAINT_VIOLATION:
+        log.info('Password change correctly rejected')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to attempt to change password: ' + str(e))
+        assert False
+
+    #
+    # Keep changing password until we fill the password history (3)
+    #
+
+    # password1
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password1')])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to change password: ' + str(e))
+        assert False
+    try:
+        topology.standalone.simple_bind_s(USER_DN, 'password1')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind as user using "password1": ' + str(e))
+        assert False
+
+    # password2
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password2')])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to change password: ' + str(e))
+        assert False
+    try:
+        topology.standalone.simple_bind_s(USER_DN, 'password2')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind as user using "password2": ' + str(e))
+        assert False
+
+    # password3
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password3')])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to change password: ' + str(e))
+        assert False
+    try:
+        topology.standalone.simple_bind_s(USER_DN, 'password3')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind as user using "password3": ' + str(e))
+        assert False
+
+    # password4
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password4')])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to change password: ' + str(e))
+        assert False
+    try:
+        topology.standalone.simple_bind_s(USER_DN, 'password4')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind as user using "password4": ' + str(e))
+        assert False
+
+    #
+    # Check that we only have 3 passwords stored in history\
+    #
+    try:
+        entry = topology.standalone.search_s(USER_DN, ldap.SCOPE_BASE,
+                                             'objectclass=*',
+                                             ['passwordHistory'])
+        pwds = entry[0].getValues('passwordHistory')
+        if len(pwds) != 3:
+            log.fatal('Incorrect number of passwords stored in histry: %d' %
+                      len(pwds))
+            assert False
+        else:
+            log.info('Correct number of passwords found in history.')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to get user entry: ' + str(e))
+        assert False
+
+    #
+    # Attempt to change the password to previous passwords
+    #
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password1')])
+        log.info('Incorrectly able to to set password to previous password1.')
+        assert False
+    except ldap.CONSTRAINT_VIOLATION:
+        log.info('Password change correctly rejected')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to attempt to change password: ' + str(e))
+        assert False
+
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password2')])
+        log.info('Incorrectly able to to set password to previous password2.')
+        assert False
+    except ldap.CONSTRAINT_VIOLATION:
+        log.info('Password change correctly rejected')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to attempt to change password: ' + str(e))
+        assert False
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password3')])
+        log.info('Incorrectly able to to set password to previous password3.')
+        assert False
+    except ldap.CONSTRAINT_VIOLATION:
+        log.info('Password change correctly rejected')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to attempt to change password: ' + str(e))
+        assert False
+
+    #
+    # Reset password by Directory Manager(admin reset)
+    #
+    try:
+        topology.standalone.simple_bind_s(DN_DM, PASSWORD)
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind as rootDN: ' + str(e))
+        assert False
+
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword',
+                                                'password-reset')])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to attempt to reset password: ' + str(e))
+        assert False
+
+    # Try and change the password to the previous password before the reset
+    try:
+        topology.standalone.simple_bind_s(USER_DN, 'password-reset')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to bind as user: ' + str(e))
+        assert False
+
+    try:
+        topology.standalone.modify_s(USER_DN, [(ldap.MOD_REPLACE,
+                                                'userpassword', 'password4')])
+        log.info('Incorrectly able to to set password to previous password4.')
+        assert False
+    except ldap.CONSTRAINT_VIOLATION:
+        log.info('Password change correctly rejected')
+    except ldap.LDAPError as e:
+        log.fatal('Failed to attempt to change password: ' + str(e))
+        assert False
+
+    log.info('Test suite PASSED.')
+
+
+if __name__ == '__main__':
+    # Run isolated
+    # -s for DEBUG mode
+    CURRENT_FILE = os.path.realpath(__file__)
+    pytest.main("-s %s" % CURRENT_FILE)
diff --git a/ldap/servers/slapd/modify.c b/ldap/servers/slapd/modify.c
index c67ef14..4bcc827 100644
--- a/ldap/servers/slapd/modify.c
+++ b/ldap/servers/slapd/modify.c
@@ -1259,6 +1259,10 @@ static int op_shared_allow_pw_change (Slapi_PBlock *pb, LDAPMod *mod, char **old
 		 * just return success.
 		 */
 		if(pw_is_pwp_admin(pb, pwpolicy)){
+			if (!SLAPI_IS_MOD_DELETE(mod->mod_op) && pwpolicy->pw_history){
+				/* Updating pw history, get the old password */
+				get_old_pw(pb, &sdn, old_pw);
+			}
 			rc = 1;
 			goto done;
 		}
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index decc29e..3b00c80 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -906,6 +906,7 @@ int check_pw_syntax( Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals,
 	char **old_pw, Slapi_Entry *e, int mod_op );
 int check_pw_syntax_ext( Slapi_PBlock *pb, const Slapi_DN *sdn, Slapi_Value **vals,
 	char **old_pw, Slapi_Entry *e, int mod_op, Slapi_Mods *smods );
+void get_old_pw( Slapi_PBlock *pb, const Slapi_DN *sdn, char **old_pw);
 int check_account_lock( Slapi_PBlock *pb, Slapi_Entry * bind_target_entry, int pwresponse_req, int account_inactivation_only /*no wire/no pw policy*/);
 int check_pw_minage( Slapi_PBlock *pb, const Slapi_DN *sdn, struct berval **vals) ;
 void add_password_attrs( Slapi_PBlock *pb, Operation *op, Slapi_Entry *e );
diff --git a/ldap/servers/slapd/pw.c b/ldap/servers/slapd/pw.c
index dea949c..ab624e7 100644
--- a/ldap/servers/slapd/pw.c
+++ b/ldap/servers/slapd/pw.c
@@ -1084,6 +1084,35 @@ retry:
 }
 
 /*
+ * Get the old password -used by password admin so we properly
+ * update pw history when reseting a password.
+ */
+void
+get_old_pw( Slapi_PBlock *pb, const Slapi_DN *sdn, char **old_pw )
+{
+    Slapi_Entry *e = NULL;
+    Slapi_Value **va = NULL;
+    Slapi_Attr *attr = NULL;
+    char *dn = (char*)slapi_sdn_get_ndn(sdn);
+
+    e = get_entry ( pb, dn );
+    if ( e == NULL ) {
+        return;
+    }
+
+    /* get current password, and remember it  */
+    attr = attrlist_find(e->e_attrs, "userpassword");
+    if ( attr && !valueset_isempty(&attr->a_present_values) ) {
+        va = valueset_get_valuearray(&attr->a_present_values);
+        *old_pw = slapi_ch_strdup(slapi_value_get_string(va[0]));
+    } else {
+        *old_pw = NULL;
+    }
+
+    slapi_entry_free(e);
+}
+
+/*
  * Basically, h0 and h1 must be longer than GENERALIZED_TIME_LENGTH.
  */
 static int
-- 
2.4.11