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 }