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    }