/* * Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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. */ package org.wso2.carbon.identity.application.authentication.framework.store; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.base.MultitenantConstants; import org.wso2.carbon.context.CarbonContext; import org.wso2.carbon.identity.application.authentication.framework.internal.FrameworkServiceDataHolder; import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException; import org.wso2.carbon.identity.base.IdentityRuntimeException; import org.wso2.carbon.identity.core.util.IdentityDatabaseUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Date; import java.util.concurrent.BlockingDeque; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingDeque; /** * Data will be persisted or stored date will be removed from the store. These two events are considered as STORE operation * and DELETE operations. * And these events are stored with unique sessionId, operation type and operation initiated timestamp. * Expired DELETE operations and related STORE operations will be deleted by a OperationCleanUpService task. * All expired operations will be deleted by SessionCleanUpService task. * */ public class SessionDataStore { private static final Log log = LogFactory.getLog(SessionDataStore.class); private static final String OPERATION_DELETE = "DELETE"; private static final String OPERATION_STORE = "STORE"; private static final String SQL_INSERT_STORE_OPERATION = "INSERT INTO IDN_AUTH_SESSION_STORE(SESSION_ID, SESSION_TYPE, OPERATION, SESSION_OBJECT, TIME_CREATED, TENANT_ID) VALUES (?,?,?,?,?,?)"; private static final String SQL_INSERT_DELETE_OPERATION = "INSERT INTO IDN_AUTH_SESSION_STORE(SESSION_ID, SESSION_TYPE,OPERATION, TIME_CREATED) VALUES (?,?,?,?)"; private static final String SQL_DELETE_STORE_OPERATIONS_TASK = "DELETE FROM IDN_AUTH_SESSION_STORE WHERE OPERATION = '"+OPERATION_STORE+"' AND SESSION_ID in (" + "SELECT SESSION_ID FROM IDN_AUTH_SESSION_STORE WHERE OPERATION = '"+OPERATION_DELETE+"' AND TIME_CREATED < ?)"; private static final String SQL_DELETE_STORE_OPERATIONS_TASK_MYSQL = "DELETE IDN_AUTH_SESSION_STORE_DELETE FROM IDN_AUTH_SESSION_STORE IDN_AUTH_SESSION_STORE_DELETE WHERE " + "OPERATION = '"+OPERATION_STORE+"' AND SESSION_ID IN (SELECT SESSION_ID FROM (SELECT SESSION_ID " + "FROM IDN_AUTH_SESSION_STORE WHERE OPERATION = '"+OPERATION_DELETE+"' AND TIME_CREATED < ?) " + "IDN_AUTH_SESSION_STORE_SELECT)"; private static final String SQL_DELETE_DELETE_OPERATIONS_TASK = "DELETE FROM IDN_AUTH_SESSION_STORE WHERE OPERATION = '"+OPERATION_DELETE+"' AND TIME_CREATED < ?"; private static final String SQL_DESERIALIZE_OBJECT_MYSQL = "SELECT OPERATION, SESSION_OBJECT, TIME_CREATED FROM IDN_AUTH_SESSION_STORE WHERE SESSION_ID =? AND" + " SESSION_TYPE=? ORDER BY TIME_CREATED DESC LIMIT 1"; private static final String SQL_DESERIALIZE_OBJECT_DB2SQL = "SELECT OPERATION, SESSION_OBJECT, TIME_CREATED FROM IDN_AUTH_SESSION_STORE WHERE SESSION_ID =? AND" + " SESSION_TYPE=? ORDER BY TIME_CREATED DESC FETCH FIRST 1 ROWS ONLY"; private static final String SQL_DESERIALIZE_OBJECT_MSSQL = "SELECT TOP 1 OPERATION, SESSION_OBJECT, TIME_CREATED FROM IDN_AUTH_SESSION_STORE WHERE SESSION_ID =? AND" + " SESSION_TYPE=? ORDER BY TIME_CREATED DESC"; private static final String SQL_DESERIALIZE_OBJECT_POSTGRESQL = "SELECT OPERATION, SESSION_OBJECT, TIME_CREATED FROM IDN_AUTH_SESSION_STORE WHERE SESSION_ID =? AND" + " SESSION_TYPE=? ORDER BY TIME_CREATED DESC LIMIT 1"; private static final String SQL_DESERIALIZE_OBJECT_INFORMIX = "SELECT FIRST 1 OPERATION, SESSION_OBJECT, TIME_CREATED FROM IDN_AUTH_SESSION_STORE WHERE SESSION_ID =? AND" + " SESSION_TYPE=? ORDER BY TIME_CREATED DESC LIMIT 1"; private static final String SQL_DESERIALIZE_OBJECT_ORACLE = "SELECT * FROM (SELECT OPERATION, SESSION_OBJECT, TIME_CREATED FROM IDN_AUTH_SESSION_STORE WHERE SESSION_ID =? AND" + " SESSION_TYPE=? ORDER BY TIME_CREATED DESC) WHERE ROWNUM < 2"; private static final String SQL_DELETE_EXPIRED_DATA_TASK = "DELETE FROM IDN_AUTH_SESSION_STORE WHERE TIME_CREATED<?"; private static final String MYSQL_DATABASE = "MySQL"; private static final String H2_DATABASE = "H2"; private static final String DB2_DATABASE = "DB2"; private static final String MS_SQL_DATABASE = "MS SQL"; private static final String MICROSOFT_DATABASE = "Microsoft"; private static final String POSTGRESQL_DATABASE = "PostgreSQL"; private static final String INFORMIX_DATABASE = "Informix"; private static int maxPoolSize = 100; private long operationCleanUpPeriod = 720; private String defaultCleanUpEnabled ="true"; private String defaultOperationCleanUpEnabled ="false"; private static BlockingDeque<SessionContextDO> sessionContextQueue = new LinkedBlockingDeque(); private static volatile SessionDataStore instance; private boolean enablePersist; private String sqlInsertSTORE; private String sqlInsertDELETE; private String sqlDeleteSTORETask; private String sqlDeleteDELETETask; private String sqlSelect; private String sqlDeleteExpiredDataTask; static { try { String maxPoolSizeConfigValue = IdentityUtil.getProperty("JDBCPersistenceManager.SessionDataPersist.PoolSize"); if (StringUtils.isNotBlank(maxPoolSizeConfigValue)) { maxPoolSize = Integer.parseInt(maxPoolSizeConfigValue); } } catch (NumberFormatException e) { if (log.isDebugEnabled()) { log.debug("Exception ignored : ", e); } log.warn("Session data persistence pool size is not configured. Using default value."); } if (maxPoolSize > 0) { log.info("Thread pool size for session persistent consumer : " + maxPoolSize); ExecutorService threadPool = Executors.newFixedThreadPool(maxPoolSize); for (int i = 0; i < maxPoolSize; i++) { threadPool.execute(new SessionDataPersistTask(sessionContextQueue)); } } } private SessionDataStore() { String enablePersistVal = IdentityUtil.getProperty("JDBCPersistenceManager.SessionDataPersist.Enable"); enablePersist = true; if (enablePersistVal != null) { enablePersist = Boolean.parseBoolean(enablePersistVal); } String insertSTORESQL = IdentityUtil .getProperty("JDBCPersistenceManager.SessionDataPersist.SQL.InsertSTORE"); String insertDELETESQL = IdentityUtil .getProperty("JDBCPersistenceManager.SessionDataPersist.SQL.InsertDELETE"); String deleteSTORETaskSQL = IdentityUtil .getProperty("JDBCPersistenceManager.SessionDataPersist.SQL.DeleteSTORETask"); String deleteDELETETaskSQL = IdentityUtil .getProperty("JDBCPersistenceManager.SessionDataPersist.SQL.DeleteDELETETask"); String selectSQL = IdentityUtil .getProperty("JDBCPersistenceManager.SessionDataPersist.SQL.Select"); String deleteExpiredDataTaskSQL = IdentityUtil .getProperty("JDBCPersistenceManager.SessionDataPersist.SQL.DeleteExpiredDataTask"); if (!StringUtils.isBlank(insertSTORESQL)) { sqlInsertSTORE = insertSTORESQL; } else { sqlInsertSTORE = SQL_INSERT_STORE_OPERATION; } if (!StringUtils.isBlank(insertDELETESQL)) { sqlInsertDELETE = insertDELETESQL; } else { sqlInsertDELETE = SQL_INSERT_DELETE_OPERATION; } if (!StringUtils.isBlank(deleteSTORETaskSQL)) { sqlDeleteSTORETask = deleteSTORETaskSQL; } if (!StringUtils.isBlank(deleteDELETETaskSQL)) { sqlDeleteDELETETask = deleteDELETETaskSQL; } else { sqlDeleteDELETETask = SQL_DELETE_DELETE_OPERATIONS_TASK; } if (!StringUtils.isBlank(selectSQL)) { sqlSelect = selectSQL; } if (!StringUtils.isBlank(deleteExpiredDataTaskSQL)) { sqlDeleteExpiredDataTask = deleteExpiredDataTaskSQL; } else { sqlDeleteExpiredDataTask = SQL_DELETE_EXPIRED_DATA_TASK; } if (!enablePersist) { log.info("Session Data Persistence of Authentication framework is not enabled."); } String isCleanUpEnabledVal = IdentityUtil.getProperty("JDBCPersistenceManager.SessionDataPersist.SessionDataCleanUp.Enable"); String isOperationCleanUpEnabledVal = IdentityUtil.getProperty("JDBCPersistenceManager.SessionDataPersist.OperationDataCleanUp.Enable"); String operationCleanUpPeriodVal = IdentityUtil.getProperty("JDBCPersistenceManager.SessionDataPersist.OperationDataCleanUp.CleanUpPeriod"); if (StringUtils.isBlank(isCleanUpEnabledVal)) { isCleanUpEnabledVal = defaultCleanUpEnabled; } if (StringUtils.isBlank(isOperationCleanUpEnabledVal)) { isOperationCleanUpEnabledVal = defaultOperationCleanUpEnabled; } if (Boolean.parseBoolean(isCleanUpEnabledVal)) { long sessionCleanupPeriod = IdentityUtil.getCleanUpPeriod( CarbonContext.getThreadLocalCarbonContext().getTenantDomain()); SessionCleanUpService sessionCleanUpService = new SessionCleanUpService(sessionCleanupPeriod/4, sessionCleanupPeriod); sessionCleanUpService.activateCleanUp(); } else { log.info("Session Data CleanUp Task of Authentication framework is not enabled."); } if (Boolean.parseBoolean(isOperationCleanUpEnabledVal)) { if (StringUtils.isNotBlank(operationCleanUpPeriodVal)) { operationCleanUpPeriod = Long.parseLong(operationCleanUpPeriodVal); } OperationCleanUpService operationCleanUpService = new OperationCleanUpService(operationCleanUpPeriod/4, operationCleanUpPeriod); operationCleanUpService.activateCleanUp(); } else { log.info("Session Data Operations CleanUp Task of Authentication framework is not enabled."); } } public static SessionDataStore getInstance() { if (instance == null) { synchronized (SessionDataStore.class) { if (instance == null) { instance = new SessionDataStore(); } } } return instance; } public Object getSessionData(String key, String type) { SessionContextDO sessionContextDO = getSessionContextData(key, type); return sessionContextDO != null ? sessionContextDO.getEntry() : null; } public SessionContextDO getSessionContextData(String key, String type) { if (!enablePersist) { return null; } Connection connection = null; try { connection = IdentityDatabaseUtil.getDBConnection(); } catch (IdentityRuntimeException e) { log.error(e.getMessage(), e); return null; } PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { if (StringUtils.isBlank(sqlSelect)) { if (connection.getMetaData().getDriverName().contains(MYSQL_DATABASE) || connection.getMetaData().getDriverName().contains(H2_DATABASE)) { sqlSelect = SQL_DESERIALIZE_OBJECT_MYSQL; } else if (connection.getMetaData().getDatabaseProductName().contains(DB2_DATABASE)) { sqlSelect = SQL_DESERIALIZE_OBJECT_DB2SQL; } else if (connection.getMetaData().getDriverName().contains(MS_SQL_DATABASE) || connection.getMetaData().getDriverName().contains(MICROSOFT_DATABASE)) { sqlSelect = SQL_DESERIALIZE_OBJECT_MSSQL; } else if (connection.getMetaData().getDriverName().contains(POSTGRESQL_DATABASE)) { sqlSelect = SQL_DESERIALIZE_OBJECT_POSTGRESQL; } else if (connection.getMetaData().getDriverName().contains(INFORMIX_DATABASE)) { // Driver name = "IBM Informix JDBC Driver for IBM Informix Dynamic Server" sqlSelect = SQL_DESERIALIZE_OBJECT_INFORMIX; } else { sqlSelect = SQL_DESERIALIZE_OBJECT_ORACLE; } } preparedStatement = connection.prepareStatement(sqlSelect); preparedStatement.setString(1, key); preparedStatement.setString(2, type); resultSet = preparedStatement.executeQuery(); if(resultSet.next()) { String operation = resultSet.getString(1); if ((OPERATION_STORE.equals(operation))) { return new SessionContextDO(key, type, getBlobObject(resultSet.getBinaryStream(2)), new Timestamp (resultSet.getLong(3))); } } } catch (ClassNotFoundException | IOException | SQLException | IdentityApplicationManagementException e) { log.error("Error while retrieving session data", e); } finally { IdentityDatabaseUtil.closeAllConnections(connection, resultSet, preparedStatement); } return null; } public void storeSessionData(String key, String type, Object entry) { storeSessionData(key, type, entry, MultitenantConstants.INVALID_TENANT_ID); } public void storeSessionData(String key, String type, Object entry, int tenantId) { if (!enablePersist) { return; } Timestamp timestamp = new Timestamp(new Date().getTime()); if (maxPoolSize > 0) { sessionContextQueue.push(new SessionContextDO(key, type, entry, timestamp)); } else { persistSessionData(key, type, entry, timestamp, tenantId); } } public void clearSessionData(String key, String type) { if (!enablePersist) { return; } Timestamp timestamp = new Timestamp(new Date().getTime()); if (maxPoolSize > 0) { sessionContextQueue.push(new SessionContextDO(key, type, null, timestamp)); } else { removeSessionData(key, type, timestamp); } } public void removeExpiredSessionData(Timestamp timestamp) { Connection connection = null; PreparedStatement statement = null; try { connection = IdentityDatabaseUtil.getDBConnection(); } catch (IdentityRuntimeException e) { log.error(e.getMessage(), e); return; } try { statement = connection.prepareStatement(sqlDeleteExpiredDataTask); statement.setLong(1, timestamp.getTime()*1000000); statement.execute(); if (!connection.getAutoCommit()) { connection.commit(); } } catch (SQLException e) { log.error("Error while removing session data from the database for the timestamp " + timestamp.toString(), e); } finally { IdentityDatabaseUtil.closeAllConnections(connection, null, statement); } } public void removeExpiredOperationData(Timestamp timestamp) { deleteSTOREOperationsTask(timestamp); deleteDELETEOperationsTask(timestamp); } public void persistSessionData(String key, String type, Object entry, Timestamp timestamp, int tenantId) { if (!enablePersist) { return; } Connection connection = null; try { connection = IdentityDatabaseUtil.getDBConnection(); } catch (IdentityRuntimeException e) { log.error(e.getMessage(), e); return; } PreparedStatement preparedStatement = null; ResultSet resultSet = null; // create a nano time stamp relative to Unix Epoch long currentStandardNano = timestamp.getTime() * 1000000; long currentSystemNano = System.nanoTime(); currentStandardNano = currentStandardNano + (currentSystemNano - FrameworkServiceDataHolder.getInstance() .getNanoTimeReference()); try { preparedStatement = connection.prepareStatement(sqlInsertSTORE); preparedStatement.setString(1, key); preparedStatement.setString(2, type); preparedStatement.setString(3, OPERATION_STORE); setBlobObject(preparedStatement, entry, 4); preparedStatement.setLong(5, currentStandardNano); preparedStatement.setInt(6, tenantId); preparedStatement.executeUpdate(); if (!connection.getAutoCommit()) { connection.commit(); } } catch (SQLException | IOException e) { log.error("Error while storing session data", e); } finally { IdentityDatabaseUtil.closeAllConnections(connection, resultSet, preparedStatement); } } public void removeSessionData(String key, String type, Timestamp timestamp) { if (!enablePersist) { return; } Connection connection = null; try { connection = IdentityDatabaseUtil.getDBConnection(); } catch (IdentityRuntimeException e) { log.error(e.getMessage(), e); return; } PreparedStatement preparedStatement = null; // create a nano time stamp relative to Unix Epoch long currentStandardNano = timestamp.getTime() * 1000000; long currentSystemNano = System.nanoTime(); currentStandardNano = currentStandardNano + (currentSystemNano - FrameworkServiceDataHolder.getInstance() .getNanoTimeReference()); try { preparedStatement = connection.prepareStatement(sqlInsertDELETE); preparedStatement.setString(1, key); preparedStatement.setString(2, type); preparedStatement.setString(3, OPERATION_DELETE); preparedStatement.setLong(4, currentStandardNano); preparedStatement.executeUpdate(); if (!connection.getAutoCommit()) { connection.commit(); } } catch (Exception e) { log.error("Error while storing DELETE operation session data", e); } finally { IdentityDatabaseUtil.closeAllConnections(connection, null, preparedStatement); } } private void setBlobObject(PreparedStatement prepStmt, Object value, int index) throws SQLException, IOException { if (value != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(value); oos.flush(); oos.close(); InputStream inputStream = new ByteArrayInputStream(baos.toByteArray()); prepStmt.setBinaryStream(index, inputStream, inputStream.available()); } else { prepStmt.setBinaryStream(index, null, 0); } } private Object getBlobObject(InputStream is) throws IdentityApplicationManagementException, IOException, ClassNotFoundException { if (is != null) { ObjectInput ois = null; try { ois = new ObjectInputStream(is); return ois.readObject(); } finally { if (ois != null) { try { ois.close(); } catch (IOException e) { log.error("IOException while trying to close ObjectInputStream.", e); } } } } return null; } private void deleteSTOREOperationsTask(Timestamp timestamp) { Connection connection = null; PreparedStatement statement = null; try { connection = IdentityDatabaseUtil.getDBConnection(); } catch (IdentityRuntimeException e) { log.error(e.getMessage(), e); return; } try { if (StringUtils.isBlank(sqlDeleteSTORETask)) { if (connection.getMetaData().getDriverName().contains(MYSQL_DATABASE)) { sqlDeleteSTORETask = SQL_DELETE_STORE_OPERATIONS_TASK_MYSQL; } else { sqlDeleteSTORETask = SQL_DELETE_STORE_OPERATIONS_TASK; } } statement = connection.prepareStatement(sqlDeleteSTORETask); statement.setLong(1, timestamp.getTime()); statement.execute(); if (!connection.getAutoCommit()) { connection.commit(); } return; } catch (SQLException e) { log.error("Error while removing STORE operation data from the database for the timestamp " + timestamp.toString(), e); } finally { IdentityDatabaseUtil.closeAllConnections(connection, null, statement); } } private void deleteDELETEOperationsTask(Timestamp timestamp) { Connection connection = null; PreparedStatement statement = null; try { connection = IdentityDatabaseUtil.getDBConnection(); } catch (IdentityRuntimeException e) { log.error(e.getMessage(), e); return; } try { statement = connection.prepareStatement(sqlDeleteDELETETask); statement.setLong(1, timestamp.getTime()); statement.execute(); if (!connection.getAutoCommit()) { connection.commit(); } return; } catch (SQLException e) { log.error("Error while removing DELETE operation data from the database for the timestamp " + timestamp.toString(), e); } finally { IdentityDatabaseUtil.closeAllConnections(connection, null, statement); } } }