diff --git a/core/org.wso2.carbon.registry.core/src/test/resources/dbscripts/h2.sql b/core/org.wso2.carbon.registry.core/src/test/resources/dbscripts/h2.sql index 1e6c4d5f2c8..996cfea7a3b 100644 --- a/core/org.wso2.carbon.registry.core/src/test/resources/dbscripts/h2.sql +++ b/core/org.wso2.carbon.registry.core/src/test/resources/dbscripts/h2.sql @@ -282,6 +282,7 @@ CREATE TABLE IF NOT EXISTS UM_ROLE ( UM_ROLE_NAME VARCHAR(255) NOT NULL, UM_TENANT_ID INTEGER DEFAULT 0, UM_SHARED_ROLE BOOLEAN DEFAULT FALSE, + UM_LAST_MODIFIED TIMESTAMP, PRIMARY KEY (UM_ID, UM_TENANT_ID), UNIQUE(UM_ROLE_NAME, UM_TENANT_ID)); diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/JDBCRealmConstants.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/JDBCRealmConstants.java index 7ad6d1ebf5b..a2a1677072e 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/JDBCRealmConstants.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/JDBCRealmConstants.java @@ -154,6 +154,8 @@ public final class JDBCRealmConstants { public static final String GET_GROUP_FILTER_WITH_LAST_MODIFIED = "GetGroupFilterWithLastModifiedSQL"; public static final String ADD_GROUP = "AddGroupSQL"; public static final String UPDATE_GROUP_NAME = "UpdateGroupNameSQL"; + public static final String UPDATE_GROUP_LAST_MODIFIED = "UpdateGroupLastModifiedTimeSQL"; + public static final String UPDATE_GROUP_LAST_MODIFIED_WITH_GROUP_ID = "UpdateGroupLastModifiedTimeWithGroupIDSQL"; public static final String COUNT_ROLES_SQL = "SELECT COUNT(UM_ROLE_NAME) AS RESULT FROM UM_ROLE WHERE UM_ROLE_NAME LIKE ? AND " + "UM_TENANT_ID = ?"; public static final String COUNT_USERS_WITH_CLAIM_SQL = "SELECT COUNT(UM_USER_ID) AS RESULT FROM UM_USER_ATTRIBUTE WHERE UM_ATTR_NAME = ? " + @@ -530,6 +532,10 @@ public final class JDBCRealmConstants { "UM_CREATED_TIME, UM_LAST_MODIFIED) VALUES (?, ?, ?, ?, ?)"; public static final String UPDATE_GROUP_NAME_SQL = "UPDATE UM_ROLE set UM_ROLE_NAME = ?, UM_LAST_MODIFIED = ? " + "WHERE UM_ROLE_UUID = ? AND UM_TENANT_ID = ?"; + public static final String UPDATE_GROUP_LAST_MODIFIED_SQL = "UPDATE UM_ROLE set UM_LAST_MODIFIED = ? " + + "WHERE UM_ROLE_NAME = ? AND UM_TENANT_ID = ?"; + public static final String UPDATE_GROUP_LAST_MODIFIED_WITH_GROUP_ID_SQL = "UPDATE UM_ROLE set " + + "UM_LAST_MODIFIED = ? WHERE UM_ROLE_UUID = ? AND UM_TENANT_ID = ?"; // properties public static final String DATASOURCE = "dataSource"; diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/JDBCUserStoreConstants.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/JDBCUserStoreConstants.java index cfe40ffcb2d..f03571357f7 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/JDBCUserStoreConstants.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/JDBCUserStoreConstants.java @@ -689,6 +689,13 @@ public class JDBCUserStoreConstants { setAdvancedProperty(JDBCRealmConstants.UPDATE_GROUP_NAME, "Update Group Name SQL", JDBCRealmConstants.UPDATE_GROUP_NAME_SQL, "", new Property[]{GROUP.getProperty(), SQL.getProperty(), FALSE.getProperty()}); + setAdvancedProperty(JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED, "Update Group Last Modified Time SQL", + JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED_SQL, "", + new Property[]{GROUP.getProperty(), SQL.getProperty(), FALSE.getProperty()}); + setAdvancedProperty(JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED_WITH_GROUP_ID, + "Update Group Last Modified Time With Group ID SQL", + JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED_WITH_GROUP_ID_SQL, "", + new Property[]{GROUP.getProperty(), SQL.getProperty(), FALSE.getProperty()}); setAdvancedProperty(JDBCRealmConstants.ADD_USER_PROPERTY, "Add User Property SQL", JDBCRealmConstants.ADD_USER_PROPERTY_SQL, "", diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCUserStoreManager.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCUserStoreManager.java index 062fd8423ce..10a3b9e9475 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCUserStoreManager.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/jdbc/UniqueIDJDBCUserStoreManager.java @@ -20,6 +20,7 @@ import org.apache.axiom.om.util.Base64; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -84,11 +85,14 @@ import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.stream.Collectors; + import javax.sql.DataSource; import static java.time.ZoneOffset.UTC; @@ -1662,6 +1666,12 @@ public void doUpdateUserListOfRoleWithID(String roleName, String[] deletedUserID if (sqlStmt2 == null) { throw new UserStoreException("The sql statement for add user to role is null."); } + + String sqlStmt3 = realmConfig.getUserStoreProperty(JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED); + if (StringUtils.isBlank(sqlStmt3) && !isShared) { + throw new UserStoreException("The sql statement for update group last modified time is null."); + } + if (deletedUserIDs != null) { if (isShared) { DatabaseUtil.udpateUserRoleMappingInBatchMode(dbConnection, sqlStmt1, roleName, tenantId, @@ -1694,6 +1704,9 @@ public void doUpdateUserListOfRoleWithID(String roleName, String[] deletedUserID } } } + if (!isShared && (deletedUserIDs != null || newUserIDs != null)) { + this.updateValuesToDatabaseWithUTCTime(dbConnection, sqlStmt3, new Date(), roleName, tenantId); + } dbConnection.commit(); } catch (SQLException e) { DatabaseUtil.rollBack(dbConnection); @@ -1713,7 +1726,6 @@ public void doUpdateUserListOfRoleWithID(String roleName, String[] deletedUserID } finally { DatabaseUtil.closeAllConnections(dbConnection); } - } @Override @@ -1789,6 +1801,8 @@ public void doUpdateRoleListOfUserWithID(String userID, String[] deletedRoles, S if (userNames.length > 1) { userID = userNames[1]; } + String sqlStmt3 = realmConfig.getUserStoreProperty(JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED); + if (deletedRoles != null && deletedRoles.length > 0) { // Break the provided role list based on whether roles are shared or not RoleBreakdown breakdown = getSharedRoleBreakdown(deletedRoles); @@ -1802,12 +1816,27 @@ public void doUpdateRoleListOfUserWithID(String userID, String[] deletedRoles, S if (sqlStmt1 == null) { throw new UserStoreException("The sql statement for remove user from role is null."); } + if (StringUtils.isBlank(sqlStmt3)) { + throw new UserStoreException("The sql statement for update group last modified time is null."); + } if (sqlStmt1.contains(UserCoreConstants.UM_TENANT_COLUMN)) { DatabaseUtil.udpateUserRoleMappingInBatchMode(dbConnection, sqlStmt1, roles, tenantId, userID, tenantId, tenantId); } else { DatabaseUtil.udpateUserRoleMappingInBatchMode(dbConnection, sqlStmt1, roles, userID); } + + // If there are common groups in new and deleted, their modified time should only be updated + // during the add operation. + if (ArrayUtils.isNotEmpty(newRoles)) { + List rolesToDeleteOnly = + (List) CollectionUtils.subtract(Arrays.asList(roles), Arrays.asList(newRoles)); + this.updateValuesToDatabaseWithUTCTimeInBatchMode(dbConnection, sqlStmt3, new Date(), + rolesToDeleteOnly, tenantId); + } else { + this.updateValuesToDatabaseWithUTCTimeInBatchMode(dbConnection, sqlStmt3, new Date(), + roles, tenantId); + } } if (sharedRoles.length > 0) { sqlStmt1 = realmConfig @@ -1849,6 +1878,9 @@ public void doUpdateRoleListOfUserWithID(String userID, String[] deletedRoles, S if (sqlStmt2 == null) { throw new UserStoreException("The sql statement for add user to role is null."); } + if (StringUtils.isBlank(sqlStmt3)) { + throw new UserStoreException("The sql statement for update group last modified time is null."); + } if (sqlStmt2.contains(UserCoreConstants.UM_TENANT_COLUMN)) { if (UserCoreConstants.OPENEDGE_TYPE.equals(type)) { DatabaseUtil @@ -1862,6 +1894,8 @@ public void doUpdateRoleListOfUserWithID(String userID, String[] deletedRoles, S } else { DatabaseUtil.udpateUserRoleMappingInBatchMode(dbConnection, sqlStmt2, newRoles, userID); } + this.updateValuesToDatabaseWithUTCTimeInBatchMode(dbConnection, sqlStmt3, new Date(), roles, + tenantId); } if (sharedRoles.length > 0) { sqlStmt2 = realmConfig.getUserStoreProperty(JDBCRealmConstants.ADD_SHARED_ROLE_TO_USER_WITH_ID); @@ -1896,7 +1930,6 @@ public void doUpdateRoleListOfUserWithID(String userID, String[] deletedRoles, S } finally { DatabaseUtil.closeAllConnections(dbConnection); } - } @Override @@ -2397,6 +2430,84 @@ private void updateValuesToDatabaseWithUTCTime(Connection dbConnection, String s } } + private void updateValuesToDatabaseWithUTCTimeInBatchMode(Connection dbConnection, String sqlStmt, + Object... params) throws UserStoreException { + + PreparedStatement prepStmt = null; + boolean localConnection = false; + try { + Instant currentInstant = Instant.now(); + if (dbConnection == null) { + localConnection = true; + dbConnection = getDBConnection(); + } + prepStmt = dbConnection.prepareStatement(sqlStmt); + int batchParamIndex = -1; + if (params != null && params.length > 0) { + for (int i = 0; i < params.length; i++) { + Object param = params[i]; + if (param == null) { + throw new UserStoreException("Invalid data provided"); + } else if (param instanceof String[]) { + batchParamIndex = i; + } else if (param instanceof String) { + if (isStoreUserAttributeAsUnicode()) { + prepStmt.setNString(i + 1, (String) param); + } else { + prepStmt.setString(i + 1, (String) param); + } + } else if (param instanceof Integer) { + prepStmt.setInt(i + 1, (Integer) param); + } else if (param instanceof Date) { + // Convert the current date-time to UTC time with ISO Date time format. + OffsetDateTime offsetDateTime = currentInstant.atOffset(ZoneOffset.UTC); + LocalDateTime localDateTime = offsetDateTime.toLocalDateTime(); + int nanoSeconds = localDateTime.getNano(); + int roundedNanoSeconds = (nanoSeconds / 1000000) * 1000000; + LocalDateTime formattedDateTime = localDateTime.withNano(roundedNanoSeconds); + prepStmt.setTimestamp(i + 1, Timestamp.valueOf(formattedDateTime)); + } else if (param instanceof Boolean) { + prepStmt.setBoolean(i + 1, (Boolean) param); + } + } + } + if (batchParamIndex != -1) { + String[] values = (String[]) params[batchParamIndex]; + for (String value : values) { + prepStmt.setString(batchParamIndex + 1, value); + prepStmt.addBatch(); + } + } + + int[] count = prepStmt.executeBatch(); + if (log.isDebugEnabled()) { + log.debug("Executed a batch update. Query is : " + sqlStmt + ": and result is" + + Arrays.toString(count)); + } + + if (localConnection) { + dbConnection.commit(); + } + } catch (SQLException e) { + DatabaseUtil.rollBack(dbConnection); + String msg = "Error occurred while updating string values to database."; + if (log.isDebugEnabled()) { + log.debug(msg, e); + } + if (e instanceof SQLIntegrityConstraintViolationException) { + // Duplicate entry + throw new UserStoreException(msg, ERROR_CODE_DUPLICATE_WHILE_WRITING_TO_DATABASE.getCode(), e); + } + // Other SQL Exception + throw new UserStoreException(msg, e); + } finally { + if (localConnection) { + DatabaseUtil.closeAllConnections(dbConnection); + } + DatabaseUtil.closeAllConnections(null, prepStmt); + } + } + public void addPropertyWithID(Connection dbConnection, String userID, String propertyName, String value, String profileName) throws UserStoreException { diff --git a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/JDBCRealmUtil.java b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/JDBCRealmUtil.java index 81e58fa4ae7..6e290408f3e 100644 --- a/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/JDBCRealmUtil.java +++ b/core/org.wso2.carbon.user.core/src/main/java/org/wso2/carbon/user/core/util/JDBCRealmUtil.java @@ -602,6 +602,14 @@ public static Map getSQL(Map properties) { if (!properties.containsKey(JDBCRealmConstants.UPDATE_GROUP_NAME)) { properties.put(JDBCRealmConstants.UPDATE_GROUP_NAME, JDBCRealmConstants.UPDATE_GROUP_NAME_SQL); } + if (!properties.containsKey(JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED)) { + properties.put(JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED, + JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED_SQL); + } + if (!properties.containsKey(JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED_WITH_GROUP_ID)) { + properties.put(JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED_WITH_GROUP_ID, + JDBCRealmConstants.UPDATE_GROUP_LAST_MODIFIED_WITH_GROUP_ID_SQL); + } //openedge if (!properties.containsKey(JDBCRealmConstants.ADD_USER_TO_ROLE_OPENEDGE)) { diff --git a/core/org.wso2.carbon.user.core/src/test/resources/dbscripts/group_uuid_disable/dbscripts/h2.sql b/core/org.wso2.carbon.user.core/src/test/resources/dbscripts/group_uuid_disable/dbscripts/h2.sql index 1e6c4d5f2c8..996cfea7a3b 100644 --- a/core/org.wso2.carbon.user.core/src/test/resources/dbscripts/group_uuid_disable/dbscripts/h2.sql +++ b/core/org.wso2.carbon.user.core/src/test/resources/dbscripts/group_uuid_disable/dbscripts/h2.sql @@ -282,6 +282,7 @@ CREATE TABLE IF NOT EXISTS UM_ROLE ( UM_ROLE_NAME VARCHAR(255) NOT NULL, UM_TENANT_ID INTEGER DEFAULT 0, UM_SHARED_ROLE BOOLEAN DEFAULT FALSE, + UM_LAST_MODIFIED TIMESTAMP, PRIMARY KEY (UM_ID, UM_TENANT_ID), UNIQUE(UM_ROLE_NAME, UM_TENANT_ID)); diff --git a/core/org.wso2.carbon.user.core/src/test/resources/dbscripts/h2.sql b/core/org.wso2.carbon.user.core/src/test/resources/dbscripts/h2.sql index d389245fc75..1ac720596c8 100644 --- a/core/org.wso2.carbon.user.core/src/test/resources/dbscripts/h2.sql +++ b/core/org.wso2.carbon.user.core/src/test/resources/dbscripts/h2.sql @@ -267,6 +267,7 @@ CREATE TABLE IF NOT EXISTS UM_ROLE ( UM_ROLE_NAME VARCHAR(255) NOT NULL, UM_TENANT_ID INTEGER DEFAULT 0, UM_SHARED_ROLE BOOLEAN DEFAULT FALSE, + UM_LAST_MODIFIED TIMESTAMP, PRIMARY KEY (UM_ID, UM_TENANT_ID), UNIQUE(UM_ROLE_NAME, UM_TENANT_ID));