c8a2f90e0b848aefaed40637d9d8086e7e64a2fc
[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         public String generateKey() {
79             byte[] trash = new byte[16];
80             char[] ctrash = new char[16];
81                 String key;
82             do {
83                 rand.nextBytes(trash);
84                 for (int i=0;i<16;i++) {
85                     trash[i]&=0x3f;
86                     ctrash[i]=(char)table.charAt(trash[i]);
87                 }
88                         key=new String(ctrash);
89             } while (null!=sessions.get(key));
90             return key;
91         }
92         
93         
94         
95         public synchronized Session findSession(String sessionId, String applicationId ) {
96                 if (sessionId==null || applicationId==null)
97                         throw new IllegalArgumentException();
98                 Session s = (Session) sessions.get(sessionId);
99                 if (s==null) {
100                         log.warn("Session not found with ID "+sessionId);
101                         return null;
102                 }
103                 if (null==s.getAuthenticationAssertion()) {
104                         log.warn("Uninitialized (reserved) Session has ID "+sessionId);
105                     return null;
106                 }
107                 if (!applicationId.equals(s.getApplicationId())) {
108                         log.error("Session ID "+sessionId+" doesn't match application "+applicationId);
109                         return null;
110                 }
111                 if (s.isExpired()) {
112                         log.error("Session ID "+sessionId+" has expired.");
113                         // return null;
114                 }
115                 s.renew();
116                 return s;
117         }
118
119         private synchronized Session findEmptySession(String sessionId) {
120                 if (sessionId==null)
121                         throw new IllegalArgumentException();
122                 Session s = (Session) sessions.get(sessionId);
123                 if (s==null) {
124                         log.warn("Session not found with ID "+sessionId);
125                         return null;
126                 }
127                 if (null!=s.getAuthenticationAssertion()){
128                         log.error("Active Session found when looking for reserved ID:"+sessionId);
129                     return null;
130                 }
131                 s.renew();
132                 return s;
133         }
134         
135         
136         protected synchronized void add(Session s) {
137                 if (s==null)
138                         throw new IllegalArgumentException();
139                 log.debug("Session added: "+s.getKey());
140                 sessions.put(s.getKey(), s);
141                 if (cache!=null)
142                         cache.add(s);
143         }
144         
145         protected synchronized void update(Session s) {
146                 if (s==null)
147                         throw new IllegalArgumentException();
148                 s.renew();
149                 log.debug("Session updated: "+s.getKey());
150                 sessions.put(s.getKey(), s);
151                 if (cache!=null)
152                         cache.update(s);
153         }
154         
155         protected synchronized void remove(Session s) {
156                 if (s==null)
157                         throw new IllegalArgumentException();
158                 log.debug("Session removed: "+s.getKey());
159                 sessions.remove(s.getKey());
160                 if (cache!=null)
161                         cache.remove(s);
162         }
163         
164         protected synchronized void expireSessions() {
165                 Iterator iterator = sessions.entrySet().iterator();
166                 while (iterator.hasNext()) {
167                         Map.Entry entry = (Map.Entry) iterator.next();
168                         Session session = (Session) entry.getValue();
169                         if (session.isExpired()) {
170                                 log.info("Session " + session.getKey() + " has expired.");
171                                 iterator.remove();
172                         }
173                 }
174         }
175         
176 //  This was generated from a C++ routine, but it doesn't seem to be needed
177 //      /**
178 //       * Test for valid Session
179 //       * 
180 //       * @param sessionId      typically, the cookie value from client browser
181 //       * @param applicationId  id of target application asking about session
182 //       * @param ipaddr         null, or IP address of client
183 //       * @return
184 //       */
185 //      public 
186 //                      boolean 
187 //      isValid(
188 //                      String sessionId,   
189 //                      String applicationId, 
190 //                      String ipaddr         
191 //                      ){
192 //              if (sessionId==null || applicationId==null)
193 //                      throw new IllegalArgumentException();
194 //              Session session = findSession(sessionId,applicationId);
195 //              ServiceProviderConfig.ApplicationInfo application = context.getServiceProviderConfig().getApplication(applicationId);
196 //              if (session==null)
197 //                      return false; // Cookie value did not match cached session
198 //              if (application == null)
199 //                      return false; // ApplicationConfig ID invalid
200 //              if (ipaddr!=null && !ipaddr.equals(session.getIpaddr()))
201 //                      return false; // Client coming from a different machine
202 //              // check for timeout
203 //              // Note: RPC prefetches attributes here
204 //              return true;
205 //      }
206
207         
208         /**
209          * Store Principal information identified by generated UUID.<br>
210          * Called from Authentication Assertion Consumer [SHIRE]
211          * 
212          * @param applicationId The application for this session
213          * @param ipaddr The client's remote IP address from HTTP
214          * @param entityId The Entity of the AA issuing the authentication
215          * @param assertion Assertion in case one needs more data
216          * @param authentication subset of assertion with handle
217          * @return String (UUID) to go in the browser cookie
218          */
219         public 
220                         String 
221         newSession(
222                         String applicationId, 
223                         String ipaddr,
224                         String entityId,
225                         SAMLAssertion assertion,
226                         SAMLAuthenticationStatement authenticationStatement,
227                         String emptySessionId // may be null
228                         ){
229                 
230                 ServiceProviderConfig config = context.getServiceProviderConfig();
231                 ApplicationInfo appinfo = config.getApplication(applicationId);
232                 Sessions appSessionValues = appinfo.getApplicationConfig().getSessions();
233                 
234                 String sessionId = null;
235                 boolean isUpdate = false;
236                 
237                 Session session;
238                 if (emptySessionId==null) {
239                     session = new Session(generateKey());
240                 } else {
241                     session = findEmptySession(emptySessionId);
242                     if (session==null) {
243                             session = new Session(generateKey());
244                     } else {
245                         isUpdate=true;
246                     }
247                 }
248                 session.setApplicationId(applicationId);
249                 session.setIpaddr(ipaddr);
250                 session.setEntityId(entityId);
251                 
252                 session.setAuthenticationAssertion(assertion);
253                 session.setAuthenticationStatement(authenticationStatement);
254                 
255                 // Get lifetime and timeout from Applications/Sessions in config file 
256                 session.setLifetime(appSessionValues.getLifetime()*1000);
257                 session.setTimeout(appSessionValues.getTimeout()*1000);
258                 
259                 sessionId = session.getKey();
260
261                 if (isUpdate)
262                         update(session);
263                 else
264                         add(session);
265                 
266             log.debug("New Session created "+sessionId);
267
268                 return sessionId;
269         }
270         public 
271         String 
272 reserveSession(
273         String applicationId 
274         ){
275
276 ServiceProviderConfig config = context.getServiceProviderConfig();
277 ApplicationInfo appinfo = config.getApplication(applicationId);
278 Sessions appSessionValues = appinfo.getApplicationConfig().getSessions();
279
280 String sessionId = null;
281 boolean isUpdate = false;
282
283 Session session= new Session(generateKey());
284 session.setApplicationId(applicationId);
285
286 sessionId = session.getKey();
287
288 add(session);
289
290 log.debug("SessionId reserved "+sessionId);
291
292 return sessionId;
293 }
294         /**
295          * <p>IOC wiring point to plug in an external SessionCache implementation.
296          * </p>
297          * 
298          * @param cache Plugin object implementing the SessionCache interface
299          */
300         public synchronized void 
301         setCache(
302                         SessionCache cache) {
303                 
304                 if (cache==null)
305                         throw new IllegalArgumentException();
306             log.info("Enabling Session Cache");
307                 /*
308                  * The following code supports dynamic switching from
309                  * one cache to another if, for example, you decide
310                  * to change databases without restarting Shibboleth.
311                  * Whether this is useful or not is a matter of dispute.
312                  */
313                 if (this.cache!=null) { // replacing an old cache
314                         this.cache.close(); // close it and leave it for GC
315                         return;
316                 }
317                 
318                 this.cache = cache; 
319                 
320                 /*
321                  * Make sure the Cache knows about in memory sessions
322                  * 
323                  * Note: The cache should probably be wired prior to letting
324                  * the Web server process requests, so in almost all cases this
325                  * block will not be neeed. However, we may allow the configuration
326                  * to change dynamically from uncached to cached in the middle
327                  * of a Shibboleth run, and this allows for that possiblity.
328                  */
329                 if (sessions.size()!=0) {
330                         for (Iterator i=sessions.values().iterator();i.hasNext();) {
331                                 Session s = (Session) i.next();
332                                 cache.add(s);
333                         }
334                 }
335                 
336                 /*
337                  * Now load any Sessions in the cache that are not in memory
338                  * (typically after a reboot).
339                  */
340                 cache.reload(sessions);
341         }
342         
343         public static StringBuffer dumpAttributes(Session session) {
344             StringBuffer sb = new StringBuffer();
345         SAMLResponse attributeResponse = session.getAttributeResponse();
346         Iterator assertions = attributeResponse.getAssertions();
347         while (assertions.hasNext()) {
348             SAMLAssertion assertion = (SAMLAssertion) assertions.next();
349             Iterator statements = assertion.getStatements();
350             while (statements.hasNext()) {
351                 SAMLStatement statement = (SAMLStatement) statements.next();
352                 if (statement instanceof SAMLAttributeStatement) {
353                     SAMLAttributeStatement attributeStatement = 
354                         (SAMLAttributeStatement) statement;
355                     
356                     // Foreach Attribute in the AttributeStatement
357                     Iterator attributes = attributeStatement.getAttributes();
358                     while (attributes.hasNext()) {
359                         SAMLAttribute attribute = 
360                             (SAMLAttribute) attributes.next();
361                         String name = attribute.getName();
362                         String namespace = attribute.getNamespace();
363                         Iterator values = attribute.getValues();
364                         while (values.hasNext()){
365                             String val = (String) values.next();
366                             sb.append(name+" "+namespace+" "+val);
367                         }
368                     }
369                 }
370             }
371         }
372             
373             return sb;
374         }
375
376         public static Map /*<String,String>*/
377         mapAttributes(Session session) {
378             Map /*<String,String>*/attributeMap = new HashMap/*<String,String>*/();
379             StringBuffer sb = new StringBuffer();
380         SAMLResponse attributeResponse = session.getAttributeResponse();
381                 if (attributeResponse==null)
382                         return attributeMap;
383         Iterator assertions = attributeResponse.getAssertions();
384         while (assertions.hasNext()) {
385             SAMLAssertion assertion = (SAMLAssertion) assertions.next();
386             Iterator statements = assertion.getStatements();
387             while (statements.hasNext()) {
388                 SAMLStatement statement = (SAMLStatement) statements.next();
389                 if (statement instanceof SAMLAttributeStatement) {
390                     SAMLAttributeStatement attributeStatement = 
391                         (SAMLAttributeStatement) statement;
392                     
393                     // Foreach Attribute in the AttributeStatement
394                     Iterator attributes = attributeStatement.getAttributes();
395                     while (attributes.hasNext()) {
396                         SAMLAttribute attribute = 
397                             (SAMLAttribute) attributes.next();
398                         String name = attribute.getName();
399                         String namespace = attribute.getNamespace();
400                         ArrayList list = new ArrayList();
401                         Iterator values = attribute.getValues();
402                         String val="";
403                         while (values.hasNext()){
404                             val = (String) values.next();
405                             list.add(val);
406                         }
407                         if (list.size()==1)
408                             attributeMap.put(name,val);
409                         else
410                             attributeMap.put(name,list);
411                     }
412                 }
413             }
414         }
415             
416             return attributeMap;
417         }
418         
419 }