You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ranger.apache.org by bp...@apache.org on 2022/04/14 04:22:26 UTC

[ranger] branch ranger-2.3 updated: RANGER-3687: Password Policy Best Practices for Strong Security

This is an automated email from the ASF dual-hosted git repository.

bpatel pushed a commit to branch ranger-2.3
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/ranger-2.3 by this push:
     new 88fa08cf9 RANGER-3687: Password Policy Best Practices for Strong Security
88fa08cf9 is described below

commit 88fa08cf93ffbacf79ded8ca5452f0365a06bafa
Author: Bhavik Patel <bh...@gmail.com>
AuthorDate: Fri Apr 8 19:23:41 2022 +0530

    RANGER-3687: Password Policy Best Practices for Strong Security
---
 .../optimized/current/ranger_core_db_mysql.sql     |  3 ++
 .../patches/059-update-x-portal-user-table.sql     | 32 ++++++++++++++
 .../optimized/current/ranger_core_db_oracle.sql    |  3 ++
 .../patches/059-update-x-portal-user-table..sql    | 26 ++++++++++++
 .../optimized/current/ranger_core_db_postgres.sql  |  3 ++
 .../patches/059-update-x-portal-user-table.sql     | 32 ++++++++++++++
 .../current/ranger_core_db_sqlanywhere.sql         |  4 ++
 .../patches/059-update-x-portal-user-table.sql     | 21 ++++++++++
 .../optimized/current/ranger_core_db_sqlserver.sql |  3 ++
 .../patches/059-update-x-portal-user-table.sql     | 23 ++++++++++
 .../main/java/org/apache/ranger/biz/UserMgr.java   | 49 +++++++++++++++++++---
 .../org/apache/ranger/entity/XXPortalUser.java     | 29 +++++++++++++
 .../conf.dist/ranger-admin-default-site.xml        |  5 +++
 .../java/org/apache/ranger/biz/TestUserMgr.java    |  1 +
 14 files changed, 229 insertions(+), 5 deletions(-)

diff --git a/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql b/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
index f6d384c06..0a6d17ffc 100644
--- a/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
+++ b/security-admin/db/mysql/optimized/current/ranger_core_db_mysql.sql
@@ -118,6 +118,8 @@ CREATE TABLE `x_portal_user` (
   `notes` varchar(4000) DEFAULT NULL,
   `other_attributes` varchar(4000) DEFAULT NULL,
   `sync_source` varchar(4000) DEFAULT NULL,
+  `old_passwords` varchar(4000) DEFAULT NULL,
+  `password_updated_time` datetime DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `x_portal_user_UK_login_id` (`login_id`),
   UNIQUE KEY `x_portal_user_UK_email` (`email`),
@@ -1809,6 +1811,7 @@ INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('056',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('057',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('058',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
+INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('059',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('DB_PATCHES',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
 
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('J10001',UTC_TIMESTAMP(),'Ranger 1.0.0',UTC_TIMESTAMP(),'localhost','Y');
diff --git a/security-admin/db/mysql/patches/059-update-x-portal-user-table.sql b/security-admin/db/mysql/patches/059-update-x-portal-user-table.sql
new file mode 100644
index 000000000..103abb5f7
--- /dev/null
+++ b/security-admin/db/mysql/patches/059-update-x-portal-user-table.sql
@@ -0,0 +1,32 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+drop procedure if exists add_old_passwords_column_to_x_portal_user_table;
+
+delimiter ;;
+create procedure add_old_passwords_column_to_x_portal_user_table() begin
+
+ if exists (select * from information_schema.columns where table_schema=database() and table_name = 'x_portal_user') then
+  if not exists (select * from information_schema.columns where table_schema=database() and table_name = 'x_portal_user' and column_name = 'old_passwords') then
+    ALTER TABLE  `x_portal_user` ADD `old_passwords` varchar(4000) DEFAULT NULL;
+    ALTER TABLE  `x_portal_user` ADD `password_updated_time` datetime DEFAULT NULL;
+  end if;
+ end if;
+end;;
+
+delimiter ;
+call add_old_passwords_column_to_x_portal_user_table();
+
+drop procedure if exists add_old_passwords_column_to_x_portal_user_table;
\ No newline at end of file
diff --git a/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql b/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql
index 28538eda5..e9a2b7e3d 100644
--- a/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql
+++ b/security-admin/db/oracle/optimized/current/ranger_core_db_oracle.sql
@@ -310,6 +310,8 @@ CREATE TABLE x_portal_user (
         notes VARCHAR(4000) DEFAULT NULL NULL ,
         other_attributes VARCHAR(4000) DEFAULT NULL NULL,
         sync_source VARCHAR(4000) DEFAULT NULL NULL,
+        old_passwords varchar(4000) DEFAULT NULL,
+        password_updated_time DATE DEFAULT NULL,
         PRIMARY KEY (id),
         CONSTRAINT x_portal_user_UK_login_id UNIQUE (login_id) ,
         CONSTRAINT x_portal_user_UK_email UNIQUE (email),
@@ -1969,6 +1971,7 @@ INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,act
 INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval, '056',sys_extract_utc(systimestamp),'Ranger 1.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval, '057',sys_extract_utc(systimestamp),'Ranger 1.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval, '058',sys_extract_utc(systimestamp),'Ranger 1.0.0',sys_extract_utc(systimestamp),'localhost','Y');
+INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval, '059',sys_extract_utc(systimestamp),'Ranger 1.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 INSERT INTO x_db_version_h (id,version,inst_at,inst_by,updated_at,updated_by,active) VALUES (X_DB_VERSION_H_SEQ.nextval, 'DB_PATCHES',sys_extract_utc(systimestamp),'Ranger 1.0.0',sys_extract_utc(systimestamp),'localhost','Y');
 
 INSERT INTO x_user_module_perm (id,user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) VALUES (X_USER_MODULE_PERM_SEQ.nextval,getXportalUIdByLoginId('admin'),getModulesIdByName('Reports'),sys_extract_utc(systimestamp),sys_extract_utc(systimestamp),getXportalUIdByLoginId('admin'),getXportalUIdByLoginId('admin'),1);
diff --git a/security-admin/db/oracle/patches/059-update-x-portal-user-table..sql b/security-admin/db/oracle/patches/059-update-x-portal-user-table..sql
new file mode 100644
index 000000000..f1dce75da
--- /dev/null
+++ b/security-admin/db/oracle/patches/059-update-x-portal-user-table..sql
@@ -0,0 +1,26 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+DECLARE
+        v_count number:=0;
+BEGIN
+        v_count:=0;
+        select count(*) into v_count from user_tab_cols where table_name='X_PORTAL_USER' and column_name='OLD_PASSWORDS';
+        if (v_count = 0) then
+                execute immediate 'ALTER TABLE x_portal_user ADD old_passwords VARCHAR(4000) DEFAULT NULL';
+                execute immediate 'ALTER TABLE x_portal_user ADD password_updated_time DATE DEFAULT NULL';
+        end if;
+        commit;
+END;/
diff --git a/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql b/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
index 047b7d403..9f3d713e6 100644
--- a/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
+++ b/security-admin/db/postgres/optimized/current/ranger_core_db_postgres.sql
@@ -193,6 +193,8 @@ user_src INT DEFAULT '0' NOT NULL,
 notes VARCHAR(4000) DEFAULT NULL NULL,
 other_attributes VARCHAR(4000) DEFAULT NULL NULL,
 sync_source VARCHAR(4000) DEFAULT NULL NULL,
+old_passwords VARCHAR(4000) DEFAULT NULL,
+password_updated_time TIMESTAMP DEFAULT NULL,
 PRIMARY KEY(id),
 CONSTRAINT x_portal_user_UK_login_id UNIQUE(login_id),
 CONSTRAINT x_portal_user_UK_email UNIQUE(email),
@@ -1892,6 +1894,7 @@ INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('056',current_timestamp,'Ranger 1.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('057',current_timestamp,'Ranger 1.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('058',current_timestamp,'Ranger 1.0.0',current_timestamp,'localhost','Y');
+INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('059',current_timestamp,'Ranger 1.0.0',current_timestamp,'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('DB_PATCHES',current_timestamp,'Ranger 1.0.0',current_timestamp,'localhost','Y');
 
 INSERT INTO x_user_module_perm (user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) VALUES
diff --git a/security-admin/db/postgres/patches/059-update-x-portal-user-table.sql b/security-admin/db/postgres/patches/059-update-x-portal-user-table.sql
new file mode 100644
index 000000000..b28b50d9a
--- /dev/null
+++ b/security-admin/db/postgres/patches/059-update-x-portal-user-table.sql
@@ -0,0 +1,32 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+select 'delimiter start';
+CREATE OR REPLACE FUNCTION add_old_passwords_column_to_x_portal_user_table()
+RETURNS void AS $$
+DECLARE
+ v_column_exists integer := 0;
+BEGIN
+ select count(*) into v_column_exists from pg_attribute where attrelid in(select oid from pg_class where relname='x_portal_user') and attname='old_passwords';
+ IF v_column_exists = 0 THEN
+  ALTER TABLE x_portal_user ADD COLUMN old_passwords VARCHAR(4000) DEFAULT NULL;
+  ALTER TABLE x_portal_user ADD COLUMN password_updated_time TIMESTAMP DEFAULT NULL;
+ END IF;
+END;
+$$ LANGUAGE plpgsql;
+select 'delimiter end';
+
+select add_old_passwords_column_to_x_portal_user_table();
+select 'delimiter end';
\ No newline at end of file
diff --git a/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql b/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql
index fabc5bf7e..980e38265 100644
--- a/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql
+++ b/security-admin/db/sqlanywhere/optimized/current/ranger_core_db_sqlanywhere.sql
@@ -218,6 +218,8 @@ create table dbo.x_portal_user(
 	notes varchar(4000) DEFAULT NULL NULL,
 	other_attributes varchar(4000) DEFAULT NULL NULL,
 	sync_source varchar(4000) DEFAULT NULL NULL,
+	old_passwords varchar(4000) DEFAULT NULL,
+	password_updated_time datetime DEFAULT NULL,
 	CONSTRAINT x_portal_user_PK_id PRIMARY KEY CLUSTERED(id),
 	CONSTRAINT x_portal_user_UK_login_id UNIQUE NONCLUSTERED (login_id)
 )
@@ -2261,6 +2263,8 @@ INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active
 GO
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('058',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
+INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('059',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
+GO
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('DB_PATCHES',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 GO
 INSERT INTO x_user_module_perm (user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) VALUES (dbo.getXportalUIdByLoginId('admin'),dbo.getModulesIdByName('Reports'),CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,dbo.getXportalUIdByLoginId('admin'),dbo.getXportalUIdByLoginId('admin'),1);
diff --git a/security-admin/db/sqlanywhere/patches/059-update-x-portal-user-table.sql b/security-admin/db/sqlanywhere/patches/059-update-x-portal-user-table.sql
new file mode 100644
index 000000000..a207b8f03
--- /dev/null
+++ b/security-admin/db/sqlanywhere/patches/059-update-x-portal-user-table.sql
@@ -0,0 +1,21 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+IF NOT EXISTS(select * from SYS.SYSCOLUMNS where tname = 'x_portal_user' and cname = 'old_passwords') THEN
+		ALTER TABLE dbo.x_portal_user ADD old_passwords varchar(4000) DEFAULT NULL;
+		ALTER TABLE dbo.x_portal_user ADD password_updated_time datetime DEFAULT NULL;
+END IF;
+GO
+exit
diff --git a/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql b/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql
index f57f59ab6..ab5b39840 100644
--- a/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql
+++ b/security-admin/db/sqlserver/optimized/current/ranger_core_db_sqlserver.sql
@@ -967,6 +967,8 @@ CREATE TABLE [dbo].[x_portal_user](
         [notes] [varchar](4000) DEFAULT NULL NULL,
         [other_attributes] [varchar](4000) DEFAULT NULL NULL,
         [sync_source] [varchar](4000) DEFAULT NULL NULL,
+        [old_passwords] [varchar](4000) DEFAULT NULL,
+        [password_updated_time] [datetime2] DEFAULT NULL,
 PRIMARY KEY CLUSTERED
 (
         [id] ASC
@@ -4111,6 +4113,7 @@ INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('056',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('057',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('058',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
+INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('059',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_db_version_h (version,inst_at,inst_by,updated_at,updated_by,active) VALUES ('DB_PATCHES',CURRENT_TIMESTAMP,'Ranger 1.0.0',CURRENT_TIMESTAMP,'localhost','Y');
 INSERT INTO x_user_module_perm (user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) VALUES (dbo.getXportalUIdByLoginId('admin'),dbo.getModulesIdByName('Reports'),CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,dbo.getXportalUIdByLoginId('admin'),dbo.getXportalUIdByLoginId('admin'),1);
 INSERT INTO x_user_module_perm (user_id,module_id,create_time,update_time,added_by_id,upd_by_id,is_allowed) VALUES (dbo.getXportalUIdByLoginId('admin'),dbo.getModulesIdByName('Resource Based Policies'),CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,dbo.getXportalUIdByLoginId('admin'),dbo.getXportalUIdByLoginId('admin'),1);
diff --git a/security-admin/db/sqlserver/patches/059-update-x-portal-user-table.sql b/security-admin/db/sqlserver/patches/059-update-x-portal-user-table.sql
new file mode 100644
index 000000000..bac2c0f1f
--- /dev/null
+++ b/security-admin/db/sqlserver/patches/059-update-x-portal-user-table.sql
@@ -0,0 +1,23 @@
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+
+GO
+IF NOT EXISTS(select * from INFORMATION_SCHEMA.columns where table_name = 'x_portal_user' and column_name = 'other_attributes')
+BEGIN
+	ALTER TABLE [dbo].[x_portal_user] ADD [old_passwords] [varchar](4000) DEFAULT NULL;
+	ALTER TABLE [dbo].[x_portal_user] ADD [password_updated_time] [datetime2] DEFAULT NULL;
+END
+GO
+exit
diff --git a/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java b/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java
index 2c50f2cab..7e55b5e1e 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/UserMgr.java
@@ -23,6 +23,7 @@ import java.io.UnsupportedEncodingException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -117,6 +118,8 @@ public class UserMgr {
 	GUIDUtil guidUtil;
 
 	private final boolean isFipsEnabled;
+	private static final int DEFAULT_PASSWORD_HISTORY_COUNT = 4;
+	private int passwordHistoryCount = PropertiesUtil.getIntProperty("ranger.password.history.count", DEFAULT_PASSWORD_HISTORY_COUNT);
 	
 	String publicRoles[] = new String[] { RangerConstants.ROLE_USER,
 			RangerConstants.ROLE_OTHER };
@@ -140,6 +143,9 @@ public class UserMgr {
 			logger.debug("UserMgr()");
 		}
 		this.isFipsEnabled = RangerAdminConfig.getInstance().isFipsEnabled();
+		if (passwordHistoryCount < 0) {
+			passwordHistoryCount = 0;
+		}
 	}
 
 	public XXPortalUser createUser(VXPortalUser userProfile, int userStatus,
@@ -160,6 +166,7 @@ public class UserMgr {
 		String saltEncodedpasswd = encrypt(user.getLoginId(),
 				user.getPassword());
 		user.setPassword(saltEncodedpasswd);
+		user.setPasswordUpdatedTime(DateUtil.getUTCDate());
 		daoManager.getXXPortalUser().create(user);
 		XXPortalUser xXPortalUser = daoManager.getXXPortalUser().findByLoginId(user.getLoginId());
 		// Create the XXPortalUserRole entries for this user
@@ -437,13 +444,29 @@ public class UserMgr {
 		}
 
 		String encryptedNewPwd = encrypt(pwdChange.getLoginId(),pwdChange.getUpdPassword());
-		//check current password and provided new password different
-		boolean isNewPasswordDifferent;
-		if (this.isFipsEnabled) {
-				isNewPasswordDifferent = isNewPasswordDifferent(pwdChange.getLoginId(), pwdChange.getOldPassword(), pwdChange.getUpdPassword());
+		String oldPasswordStr = gjUser.getOldPasswords();
+		List<String> oldPasswords;
+
+		if (StringUtils.isNotEmpty(oldPasswordStr)) {
+			oldPasswords = new ArrayList<>(Arrays.asList(oldPasswordStr.split(",")));
+		} else {
+			oldPasswords = new ArrayList<>();
+		}
+		oldPasswords.add(gjUser.getPassword());
+		while (oldPasswords.size() > this.passwordHistoryCount) {
+			oldPasswords.remove(0);
+		}
+		boolean isNewPasswordDifferent = oldPasswords.isEmpty();
+		for (String oldPassword : oldPasswords) {
+			if (this.isFipsEnabled) {
+				isNewPasswordDifferent = isNewPasswordDifferent(pwdChange.getLoginId(), oldPassword, encryptedNewPwd);
 			} else {
-				isNewPasswordDifferent = !encryptedNewPwd.equals(currentPassword);
+				isNewPasswordDifferent = !encryptedNewPwd.equals(oldPassword);
 			}
+			if (!isNewPasswordDifferent){
+				break;
+			}
+		}
 			if (isNewPasswordDifferent) {
 				List<XXTrxLog> trxLogList = new ArrayList<XXTrxLog>();
 				XXTrxLog xTrxLog = new XXTrxLog();
@@ -457,6 +480,7 @@ public class UserMgr {
 				trxLogList.add(xTrxLog);
 	                        rangerBizUtil.createTrxLog(trxLogList);
 				gjUser.setPassword(encryptedNewPwd);
+				updateOldPasswords(gjUser, oldPasswords);
 				gjUser = daoManager.getXXPortalUser().update(gjUser);
 				ret.setMsgDesc("Password successfully updated");
 				ret.setStatusCode(VXResponse.STATUS_SUCCESS);
@@ -469,6 +493,12 @@ public class UserMgr {
 		return ret;
 	}
 
+	private void updateOldPasswords(XXPortalUser gjUser, List<String> oldPasswords) {
+		String oldPasswordStr = CollectionUtils.isNotEmpty(oldPasswords) ? StringUtils.join(oldPasswords, ",") : null;
+		gjUser.setOldPasswords(oldPasswordStr);
+		gjUser.setPasswordUpdatedTime(DateUtil.getUTCDate());
+	}
+
 	/**
 	 * @param gjUser
 	 * @param changeEmail
@@ -1338,6 +1368,15 @@ public class UserMgr {
 			String encryptedNewPwd = encrypt(xXPortalUser.getLoginId(),
 					updatedPassword);
             if (xXPortalUser.getUserSource() != RangerCommonEnums.USER_EXTERNAL) {
+				String oldPasswordsStr = xXPortalUser.getOldPasswords();
+				List<String> oldPasswords;
+				if (StringUtils.isNotEmpty(oldPasswordsStr)) {
+					oldPasswords = new ArrayList<>(Arrays.asList(oldPasswordsStr.split(",")));
+				} else {
+					oldPasswords = new ArrayList<>();
+				}
+				oldPasswords.add(encryptedNewPwd);
+				updateOldPasswords(xXPortalUser, oldPasswords);
 		xXPortalUser.setPassword(encryptedNewPwd);
              }
              xXPortalUser = daoManager.getXXPortalUser().update(xXPortalUser);
diff --git a/security-admin/src/main/java/org/apache/ranger/entity/XXPortalUser.java b/security-admin/src/main/java/org/apache/ranger/entity/XXPortalUser.java
index d0451b4d2..2ff3f6996 100644
--- a/security-admin/src/main/java/org/apache/ranger/entity/XXPortalUser.java
+++ b/security-admin/src/main/java/org/apache/ranger/entity/XXPortalUser.java
@@ -31,11 +31,16 @@ import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.SequenceGenerator;
 import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
 import javax.xml.bind.annotation.XmlRootElement;
 
 import org.apache.ranger.common.AppConstants;
+import org.apache.ranger.common.DateUtil;
 import org.apache.ranger.common.RangerConstants;
 
+import java.util.Date;
+
 
 @Entity
 @Table(name="x_portal_user")
@@ -165,6 +170,13 @@ public class XXPortalUser extends XXDBBase implements java.io.Serializable {
 	@Column(name="SYNC_SOURCE")
 	protected String syncSource;
 
+	@Column(name="OLD_PASSWORDS")
+	protected String oldPasswords;
+
+	@Temporal(TemporalType.TIMESTAMP)
+	@Column(name="PASSWORD_UPDATED_TIME")
+	protected Date passwordUpdatedTime = DateUtil.getUTCDate();
+
 	/**
 	 * Default constructor. This will set all the attributes to default value.
 	 */
@@ -363,6 +375,22 @@ public class XXPortalUser extends XXDBBase implements java.io.Serializable {
 	 */
 	public String getSyncSource() { return syncSource; }
 
+	public String getOldPasswords() {
+		return oldPasswords;
+	}
+
+	public void setOldPasswords(String oldPasswords) {
+		this.oldPasswords = oldPasswords;
+	}
+
+	public Date getPasswordUpdatedTime() {
+		return passwordUpdatedTime;
+	}
+
+	public void setPasswordUpdatedTime(Date passwordUpdatedTime) {
+		this.passwordUpdatedTime = passwordUpdatedTime;
+	}
+
 	/**
 	 * This return the bean content in string format
 	 * @return formatedStr
@@ -381,6 +409,7 @@ public class XXPortalUser extends XXDBBase implements java.io.Serializable {
 		str += "notes={" + notes + "} ";
 		str += "otherAttributes={" + otherAttributes + "} ";
 		str += "syncSource={" + syncSource + "} ";
+		str += "passwordUpdatedTime={" + passwordUpdatedTime + "} ";
 		str += "}";
 		return str;
 	}
diff --git a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
index bf72ff3b0..58f434da5 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-default-site.xml
@@ -511,6 +511,11 @@
 		<value>false</value>
 		<description></description>
 	</property>
+	<property>
+		<name>ranger.password.history.count</name>
+		<value>4</value>
+		<description></description>
+	</property>
 	<!-- # DB Info for audit_DB -->
 
 	<property>
diff --git a/security-admin/src/test/java/org/apache/ranger/biz/TestUserMgr.java b/security-admin/src/test/java/org/apache/ranger/biz/TestUserMgr.java
index f43b30196..b6c43133b 100644
--- a/security-admin/src/test/java/org/apache/ranger/biz/TestUserMgr.java
+++ b/security-admin/src/test/java/org/apache/ranger/biz/TestUserMgr.java
@@ -2036,6 +2036,7 @@ public class TestUserMgr {
 		user.setLoginId(userProfile.getLoginId());
 		String encryptCred = userMgr.encrypt(userProfile.getLoginId(), userProfile.getPassword());
 		user.setPassword(encryptCred);
+		user.setOldPasswords(encryptCred);
 		Mockito.when(daoManager.getXXPortalUser()).thenReturn(userDao);
 		Mockito.when(userDao.findByLoginId(Mockito.anyString())).thenReturn(user);
 		Mockito.when(stringUtil.equals(Mockito.anyString(), Mockito.nullable(String.class))).thenReturn(true);