Cleanup comments, remove unused code
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / SessionManager.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * SessionManager creates, maintains, and caches Session objects.
19  * 
20  * The SessionManager is a singleton object.
21  * A reference to the unique SessionManger object can always be obtained
22  * from the ServiceProviderContext.getSessionManager() method.
23  * 
24  * Sessions should only be created, modified, and deleted through methods
25  * of this class so that the in-memory collection and any disk Cache can
26  * also be changed. Disk cache implementations are referenced through the
27  * SessionCache interface. 
28  */
29 package edu.internet2.middleware.shibboleth.serviceprovider;
30
31 import java.security.SecureRandom;
32 import java.util.ArrayList;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.Map;
36 import java.util.TreeMap;
37
38 import org.apache.log4j.Logger;
39 import org.opensaml.SAMLAssertion;
40 import org.opensaml.SAMLAttribute;
41 import org.opensaml.SAMLAttributeStatement;
42 import org.opensaml.SAMLAuthenticationStatement;
43 import org.opensaml.SAMLResponse;
44 import org.opensaml.SAMLStatement;
45
46 import x0.maceShibbolethTargetConfig1.SessionsDocument.Sessions;
47
48 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
49
50 /**
51  * <p>SessionManager manages the memory and disk Cache of Session objects.</p>
52  * 
53  * <p>setSessionCache(SessionCache s) is an "IOC" wiring point. Pass it
54  * an implementation of the SessionCache interface.</p> 
55  * 
56  * @author Howard Gilbert
57  */
58 public class SessionManager {
59         
60         /*
61          * Sessions can be saved using any Persistance Framework. If a Cache
62          * is created, the following pointer is filled in and we start to 
63          * use it.
64          */
65         private static Logger log = Logger.getLogger(SessionManager.class.getName());
66         
67         private SessionCache cache = null; // By default, use memory cache only
68         
69         private TreeMap sessions = new TreeMap(); // The memory cache of Sessions
70         
71         private static ServiceProviderContext context = ServiceProviderContext.getInstance();
72
73         private static SecureRandom rand = new SecureRandom();
74         private static final String table = "0123456789" +
75                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
76                 "abcdefgjikjlmnopqrstuvwxyz"+
77                 "$@";
78     
79     /**
80      * Generate a 16 byte random ASCII string.
81      * @return generated string
82      */
83         public String generateKey() {
84             byte[] trash = new byte[16];
85             char[] ctrash = new char[16];
86                 String key;
87             do {
88                 rand.nextBytes(trash);
89                 for (int i=0;i<16;i++) {
90                     trash[i]&=0x3f;
91                     ctrash[i]=(char)table.charAt(trash[i]);
92                 }
93                         key=new String(ctrash);
94             } while (null!=sessions.get(key));
95             return key;
96         }
97         
98         
99         /**
100      * Find a Session object given its sessionID key. 
101      * 
102      * <p>Will not match uninitialized Sessions.</p>
103      * 
104      * @param sessionId ID and key of session
105      * @param applicationId Sanity check, must match session contents
106      * @return null if not found, else Session
107          */
108         public synchronized Session findSession(String sessionId, String applicationId ) {
109                 if (sessionId==null || applicationId==null)
110                         throw new IllegalArgumentException();
111                 Session s = (Session) sessions.get(sessionId);
112                 if (s==null) {
113                         log.warn("Session not found with ID "+sessionId);
114                         return null;
115                 }
116                 if (null==s.getAuthenticationAssertion()) {
117                         log.warn("Uninitialized (reserved) Session has ID "+sessionId);
118                     return null;
119                 }
120                 if (!applicationId.equals(s.getApplicationId())) {
121                         log.error("Session ID "+sessionId+" doesn't match application "+applicationId);
122                         return null;
123                 }
124                 if (s.isExpired()) {
125                         log.error("Session ID "+sessionId+" has expired.");
126                         return null;
127                 }
128                 s.renew();
129                 return s;
130         }
131
132     /**
133      * Locate an empty Session block reserved by a call from the RM.
134      * This is a test on the validity of a claimed reserved SessionID.
135      * 
136      * @param sessionId
137      * @return Session block (uninitialized).
138      */
139         private synchronized Session findEmptySession(String sessionId) {
140                 if (sessionId==null)
141                         throw new IllegalArgumentException();
142                 Session s = (Session) sessions.get(sessionId);
143                 if (s==null) {
144                         log.warn("Session not found with ID "+sessionId);
145                         return null;
146                 }
147                 if (null!=s.getAuthenticationAssertion()){
148                         log.error("Active Session found when looking for reserved ID:"+sessionId);
149                     return null;
150                 }
151                 s.renew();
152                 return s;
153         }
154         
155         
156     /**
157      * Called internally to add a Session block to the cache.
158      * @param s Session
159      */
160         protected synchronized void add(Session s) {
161                 if (s==null)
162                         throw new IllegalArgumentException();
163                 log.debug("Session added: "+s.getKey());
164                 sessions.put(s.getKey(), s);
165                 if (cache!=null)
166                         cache.add(s);
167         }
168         
169     /**
170      * Called internally to replace a Session block in the cache, 
171      * or more commonly to replace a block with itself and then just
172      * notify the cache that it has been refreshed.
173      * 
174      * @param s Session
175      */
176         protected synchronized void update(Session s) {
177                 if (s==null)
178                         throw new IllegalArgumentException();
179                 s.renew();
180                 log.debug("Session updated: "+s.getKey());
181                 sessions.put(s.getKey(), s);
182                 if (cache!=null)
183                         cache.update(s);
184         }
185         
186     /**
187      * Called internally to remove a Session from the cache. Since
188      * there is no logout, this is called when a Session expires.
189      * 
190      * @param s Session
191      */
192         protected synchronized void remove(Session s) {
193                 if (s==null)
194                         throw new IllegalArgumentException();
195                 log.debug("Session removed: "+s.getKey());
196                 sessions.remove(s.getKey());
197                 if (cache!=null)
198                         cache.remove(s);
199         }
200         
201     /**
202      * Called by a timer driven process to check for expired
203      * Sessions.
204      *
205      */
206         protected synchronized void expireSessions() {
207                 Iterator iterator = sessions.entrySet().iterator();
208                 while (iterator.hasNext()) {
209                         Map.Entry entry = (Map.Entry) iterator.next();
210                         Session session = (Session) entry.getValue();
211                         if (session.isExpired()) {
212                                 log.info("Session " + session.getKey() + " has expired.");
213                                 iterator.remove();
214                         }
215                 }
216         }
217         
218
219         
220         /**
221          * Store Principal information identified by generated UUID.<br>
222          * Called from Authentication Assertion Consumer [SHIRE]
223          * 
224          * @param applicationId The application for this session
225          * @param ipaddr The client's remote IP address from HTTP
226          * @param entityId The Entity of the AA issuing the authentication
227          * @param assertion Assertion in case one needs more data
228          * @param authentication subset of assertion with handle
229          * @return String (UUID) to go in the browser cookie
230          */
231         public 
232                         String 
233         newSession(
234                         String applicationId, 
235                         String ipaddr,
236                         String entityId,
237                         SAMLAssertion assertion,
238                         SAMLAuthenticationStatement authenticationStatement,
239                         String emptySessionId // may be null
240                         ){
241                 
242                 ServiceProviderConfig config = context.getServiceProviderConfig();
243                 ApplicationInfo appinfo = config.getApplication(applicationId);
244                 Sessions appSessionValues = appinfo.getApplicationConfig().getSessions();
245                 
246                 String sessionId = null;
247                 boolean isUpdate = false;
248                 
249                 Session session;
250                 if (emptySessionId==null) {
251                     session = new Session(generateKey());
252                 } else {
253                     session = findEmptySession(emptySessionId);
254                     if (session==null) {
255                             session = new Session(generateKey());
256                     } else {
257                         isUpdate=true;
258                     }
259                 }
260                 session.setApplicationId(applicationId);
261                 session.setIpaddr(ipaddr);
262                 session.setEntityId(entityId);
263                 
264                 session.setAuthenticationAssertion(assertion);
265                 session.setAuthenticationStatement(authenticationStatement);
266                 
267                 // Get lifetime and timeout from Applications/Sessions in config file 
268                 session.setLifetime(appSessionValues.getLifetime()*1000);
269                 session.setTimeout(appSessionValues.getTimeout()*1000);
270                 
271                 sessionId = session.getKey();
272
273                 if (isUpdate)
274                         update(session);
275                 else
276                         add(session);
277                 
278             log.debug("New Session created "+sessionId);
279
280                 return sessionId;
281         }
282     
283     /**
284      * Create an empty Session object. Reserves a SessionId for the RM
285      * that can later be filled in with data from the SSO Assertion.
286      * 
287      * @param applicationId The <Application> associated with the Session.
288      * @return
289      */
290         public 
291         String 
292 reserveSession(
293         String applicationId 
294         ){
295
296             String sessionId = null;
297             Session session= new Session(generateKey());
298             session.setApplicationId(applicationId);
299             
300             sessionId = session.getKey();
301             
302             add(session);
303             
304             log.debug("SessionId reserved "+sessionId);
305             
306             return sessionId;
307 }
308         /**
309          * <p>IOC wiring point to plug in an external SessionCache implementation.
310          * </p>
311          * 
312          * @param cache Plugin object implementing the SessionCache interface
313          */
314         public synchronized void 
315         setCache(
316                         SessionCache cache) {
317                 
318                 if (cache==null)
319                         throw new IllegalArgumentException();
320             log.info("Enabling Session Cache");
321                 /*
322                  * The following code supports dynamic switching from
323                  * one cache to another if, for example, you decide
324                  * to change databases without restarting Shibboleth.
325                  * Whether this is useful or not is a matter of dispute.
326                  */
327                 if (this.cache!=null) { // replacing an old cache
328                         this.cache.close(); // close it and leave it for GC
329                         return;
330                 }
331                 
332                 this.cache = cache; 
333                 
334                 /*
335                  * Make sure the Cache knows about in memory sessions
336                  * 
337                  * Note: The cache should probably be wired prior to letting
338                  * the Web server process requests, so in almost all cases this
339                  * block will not be neeed. However, we may allow the configuration
340                  * to change dynamically from uncached to cached in the middle
341                  * of a Shibboleth run, and this allows for that possiblity.
342                  */
343                 if (sessions.size()!=0) {
344                         for (Iterator i=sessions.values().iterator();i.hasNext();) {
345                                 Session s = (Session) i.next();
346                                 cache.add(s);
347                         }
348                 }
349                 
350                 /*
351                  * Now load any Sessions in the cache that are not in memory
352                  * (typically after a reboot).
353                  */
354                 cache.reload(sessions);
355         }
356         
357     /**
358      * Diagnostic routine not used during normal processing.
359      * 
360      * @param session
361      * @return
362      */
363         public static StringBuffer dumpAttributes(Session session) {
364             StringBuffer sb = new StringBuffer();
365         SAMLResponse attributeResponse = session.getAttributeResponse();
366         Iterator assertions = attributeResponse.getAssertions();
367         while (assertions.hasNext()) {
368             SAMLAssertion assertion = (SAMLAssertion) assertions.next();
369             Iterator statements = assertion.getStatements();
370             while (statements.hasNext()) {
371                 SAMLStatement statement = (SAMLStatement) statements.next();
372                 if (statement instanceof SAMLAttributeStatement) {
373                     SAMLAttributeStatement attributeStatement = 
374                         (SAMLAttributeStatement) statement;
375                     
376                     // Foreach Attribute in the AttributeStatement
377                     Iterator attributes = attributeStatement.getAttributes();
378                     while (attributes.hasNext()) {
379                         SAMLAttribute attribute = 
380                             (SAMLAttribute) attributes.next();
381                         String name = attribute.getName();
382                         String namespace = attribute.getNamespace();
383                         Iterator values = attribute.getValues();
384                         while (values.hasNext()){
385                             String val = (String) values.next();
386                             sb.append(name+" "+namespace+" "+val);
387                         }
388                     }
389                 }
390             }
391         }
392             
393             return sb;
394         }
395
396     /**
397      * Extract all attributes from the Session in a simpler format than
398      * the complete SAML structure.
399      * 
400      * @param session
401      * @return
402      */
403         public static Map /*<String,String>*/
404         mapAttributes(Session session) {
405             Map /*<String,String>*/attributeMap = new HashMap/*<String,String>*/();
406             SAMLResponse attributeResponse = session.getAttributeResponse();
407                 if (attributeResponse==null)
408                         return attributeMap;
409         Iterator assertions = attributeResponse.getAssertions();
410         while (assertions.hasNext()) {
411             SAMLAssertion assertion = (SAMLAssertion) assertions.next();
412             Iterator statements = assertion.getStatements();
413             while (statements.hasNext()) {
414                 SAMLStatement statement = (SAMLStatement) statements.next();
415                 if (statement instanceof SAMLAttributeStatement) {
416                     SAMLAttributeStatement attributeStatement = 
417                         (SAMLAttributeStatement) statement;
418                     
419                     // Foreach Attribute in the AttributeStatement
420                     Iterator attributes = attributeStatement.getAttributes();
421                     while (attributes.hasNext()) {
422                         SAMLAttribute attribute = 
423                             (SAMLAttribute) attributes.next();
424                         String name = attribute.getName();
425                         String namespace = attribute.getNamespace();
426                         ArrayList list = new ArrayList();
427                         Iterator values = attribute.getValues();
428                         String val="";
429                         while (values.hasNext()){
430                             val = (String) values.next();
431                             list.add(val);
432                         }
433                         if (list.size()==1)
434                             attributeMap.put(name,val);
435                         else
436                             attributeMap.put(name,list);
437                     }
438                 }
439             }
440         }
441             
442             return attributeMap;
443         }
444         
445 }