001 /** 002 * Copyright 2005-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.kim.sesn; 017 018 import java.util.Date; 019 import java.util.HashMap; 020 import java.util.Iterator; 021 import java.util.List; 022 import java.util.Map; 023 024 import org.apache.commons.logging.Log; 025 import org.apache.commons.logging.LogFactory; 026 import org.kuali.rice.kim.sesn.timeouthandlers.TimeoutHandler; 027 import org.springframework.dao.IncorrectResultSizeDataAccessException; 028 import org.springframework.jdbc.core.JdbcTemplate; 029 030 /** 031 * This class is used to interface with the distributed session database. 032 * 033 * TODO: add clear principals to clearSesn 034 * @author Kuali Rice Team (rice.collab@kuali.org) 035 * 036 */ 037 public class DistributedSession { 038 public static final String DEFAULT_PREFIX="DST"; 039 private static String prefix = DEFAULT_PREFIX; 040 private JdbcTemplate jdbcTemplate; 041 private TimeoutHandler timeoutHandler; 042 private boolean allowInsertOnTouch = false; 043 044 private static final Log logger = LogFactory.getLog(DistributedSession.class); 045 046 /** 047 * @param timeoutHandler the timeoutHandler to set 048 */ 049 public void setTimeoutHandler(TimeoutHandler timeoutHandler) { 050 this.timeoutHandler = timeoutHandler; 051 } 052 053 /** 054 * @param jdbcTemplate the jdbcTemplate to set 055 */ 056 public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { 057 this.jdbcTemplate = jdbcTemplate; 058 } 059 060 /** 061 * This method determines if the Session Ticket is valid. 062 * 063 * @param DST - the Distributed Session Ticket 064 * @return true if the session is valid 065 */ 066 public boolean isSesnValid(String DST) { 067 logger.debug("isSesnValid(DST)"); 068 return isSesnValid (DST, new HashMap<String,Object>()); 069 } 070 071 /** 072 * This method determines if the Session Ticket is valid. 073 * 074 * @param DST - the Distributed Session Ticket 075 * @param timoutArgs - Additional information on which to base timeouts 076 * @return true if the session is valid 077 */ 078 public boolean isSesnValid(String DST, Map<String,Object> timeoutArgs) { 079 logger.debug("isSesnValid(DST, timeoutArgs)"); 080 boolean bRet = false; 081 String sql = "select sesnID, lastAccessDt, maxIdleTime from authnsesn where sesnID=?"; 082 083 if (DST != null) { 084 Object[] args = { DST }; 085 086 try { 087 Map<String,Object> fields = jdbcTemplate.queryForMap(sql, args); 088 fields.put("maxIdleTime", this.getMaxIdleTime((Long)fields.get("maxIdleTime"), (Date)fields.get("lastAccessDt"))); 089 fields.putAll(timeoutArgs); 090 091 if (logger.isDebugEnabled()) { 092 logger.debug("ARGUMENTS number:" + fields.size()); 093 for (Iterator<Map.Entry<String,Object>> i = fields.entrySet().iterator(); i.hasNext(); ) { 094 Map.Entry<String,Object> entry = (Map.Entry<String,Object>)i.next(); 095 logger.debug("ARGUMENT " + entry.getKey() + ":" + entry.getValue()); 096 } 097 } 098 099 if(!timeoutHandler.hasTimedOut(fields)) { 100 logger.debug("Session not timed out"); 101 bRet = true; 102 } else { 103 logger.debug("Session timed out"); 104 } 105 } catch (Exception e) { 106 logger.debug(e); 107 } 108 } 109 else { 110 logger.debug("Session ID is null"); 111 } 112 113 return bRet; 114 } 115 116 117 /** 118 * This method returns the list of principals currently authenticated 119 * that are associated with the provided Distributed Session Ticket 120 * 121 * @param DST - the Distributed Session Ticket 122 * @return the List of authenticated principals 123 */ 124 public List<String> getAuthenticatedPricipals(String DST) { 125 String sql = "select principalID from authnsesn where sesnID=?"; 126 Object args[] = { DST }; 127 128 return jdbcTemplate.queryForList(sql, args, String.class); 129 } 130 131 /** 132 * This method removes the session 133 * 134 * @param DST - the Distributed Session Ticket 135 */ 136 public void clearSesn(String DST) { 137 String sql = "delete from authnsesn where sesnID='" + DST + "'"; 138 139 jdbcTemplate.execute(sql); 140 } 141 142 143 /** 144 * This method takes an authenticated principal and generates a 145 * Distributed Session Ticket and updates the session database 146 * 147 * @param principalID - the id of the authenticated entity 148 * @return DST - the Distributed Session Ticket 149 */ 150 public String createSesn(String principalID) { 151 String DST = this.generateDST(); 152 153 this.touchSesn(DST); 154 this.addPrincipalToSesn(DST, principalID); 155 156 return DST; 157 } 158 159 /** 160 * This method generates a unique Distributed Session Ticket 161 * 162 * @return DST - the Distributed Session Ticket 163 */ 164 public String generateDST() { 165 return prefix + "-" + SessionIdGenerator.getNewString(); 166 } 167 168 /** 169 * This method updates the session 170 * 171 * @param DST - the Distributed Session Ticket 172 */ 173 public void touchSesn(String DST) { 174 String sql = "select lastAccessDt, maxIdleTime from authnsesn where sesnID=?"; 175 String updateSql = ""; 176 Object[] args = { DST }, 177 updateArgs; 178 Long maxIdleTime; 179 180 try { 181 if (logger.isDebugEnabled()) { 182 logger.debug("ARGUMENTS number:" + args.length); 183 logger.debug("ARGUMENTS 0:" + args[0]); 184 } 185 Map<String,Object> fields = jdbcTemplate.queryForMap(sql, args); 186 Date lastAccessDt = (Date)fields.get("lastAccessDt"); 187 if (logger.isDebugEnabled()) { 188 logger.debug("Last Access:" + lastAccessDt); 189 } 190 maxIdleTime = getMaxIdleTime((Long)fields.get("maxIdleTime"), lastAccessDt); 191 192 193 updateSql = "update authnsesn set lastAccessDt=NOW(), maxIdleTime = ? where sesnID=?"; 194 updateArgs = new Object[] { maxIdleTime, DST }; 195 jdbcTemplate.update(updateSql, updateArgs); 196 } 197 // catch if no or more than 1 results are returned 198 catch (IncorrectResultSizeDataAccessException ex) { 199 if (this.allowInsertOnTouch) { 200 maxIdleTime = new Long(0); 201 202 updateSql = "insert into authnsesn (sesnID, insertDt, lastAccessDt, maxIdleTime) values (?, NOW(), NOW(), ?)"; 203 updateArgs = new Object[] { DST, maxIdleTime }; 204 jdbcTemplate.update(updateSql, updateArgs); 205 } 206 } 207 } 208 209 210 /** 211 * This method returns the greater of the stored max idle time and 212 * time since last access 213 * 214 * @param oldMaxIdleTime - the previous max idle time 215 * @param lastAccessDt - the timestamp of last access 216 * @return the max idle time 217 */ 218 public Long getMaxIdleTime(Long oldMaxIdleTime, Date lastAccessDt) { 219 Long maxIdleTime = oldMaxIdleTime; 220 221 if (logger.isDebugEnabled()) { 222 logger.debug("Max Idle:" + maxIdleTime); 223 } 224 long curIdleTime = System.currentTimeMillis()-lastAccessDt.getTime(); 225 if (logger.isDebugEnabled()) { 226 logger.debug("Curr Idle:" + curIdleTime); 227 } 228 if (curIdleTime > maxIdleTime) { 229 maxIdleTime = new Long(curIdleTime); 230 } 231 232 return maxIdleTime; 233 } 234 235 /** 236 * This method appends a principal to an active session 237 * 238 * @param DST - the Distributed Session Ticket 239 * @param principalID - the id of the authenticated entity 240 */ 241 public void addPrincipalToSesn(String DST, String principalID) { 242 // this will fail if the record already exists 243 try { 244 String updateSql = "insert into authnsesnprincipal (sesnID, principalID) values (?, ?)"; 245 246 jdbcTemplate.update(updateSql, new Object[] { DST, principalID }); 247 if (logger.isDebugEnabled()) { 248 logger.debug("Added Principal to Sesn:" + principalID + " " + DST); 249 } 250 } 251 catch (Exception e) { 252 if (logger.isDebugEnabled()) { 253 logger.debug("Principal Probably already exists:" + principalID + " " + DST); 254 } 255 } 256 } 257 258 /** 259 * @return the prefix 260 */ 261 public static String getPrefix() { 262 return DistributedSession.prefix; 263 } 264 265 /** 266 * @param prefix the prefix to set 267 */ 268 public static void setPrefix(String prefix) { 269 DistributedSession.prefix = prefix; 270 } 271 272 /** 273 * @param allowInsertOnTouch the allowInsertOnTouch to set 274 */ 275 public void setAllowInsertOnTouch(boolean allowInsertOnTouch) { 276 this.allowInsertOnTouch = allowInsertOnTouch; 277 } 278 }