7036016d1c4c1cf4febf614f901670643baeffcf
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / SessionManager.java
1 /*
2  * SessionManager creates, maintains, and caches Session objects.
3  * 
4  * The SessionManager is a singleton object.
5  * A reference to the unique SessionManger object can always be obtained
6  * from the ServiceProviderContext.getSessionManager() method.
7  * 
8  * Sessions should only be created, modified, and deleted through methods
9  * of this class so that the in-memory collection and any disk Cache can
10  * also be changed. Disk cache implementations are referenced through the
11  * SessionCache interface. 
12  * 
13  * --------------------
14  * Copyright 2002, 2004 
15  * University Corporation for Advanced Internet Development, Inc. 
16  * All rights reserved
17  * [Thats all we have to say to protect ourselves]
18  * Your permission to use this code is governed by "The Shibboleth License".
19  * A copy may be found at http://shibboleth.internet2.edu/license.html
20  * [Nothing in copyright law requires license text in every file.]
21  */
22 package edu.internet2.middleware.shibboleth.serviceprovider;
23
24 import java.security.SecureRandom;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Map;
29 import java.util.TreeMap;
30
31 import org.apache.log4j.Logger;
32 import org.opensaml.SAMLAssertion;
33 import org.opensaml.SAMLAttribute;
34 import org.opensaml.SAMLAttributeStatement;
35 import org.opensaml.SAMLAuthenticationStatement;
36 import org.opensaml.SAMLResponse;
37 import org.opensaml.SAMLStatement;
38
39 import x0.maceShibbolethTargetConfig1.SessionsDocument.Sessions;
40
41 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
42
43 /**
44  * <p>SessionManager manages the memory and disk Cache of Session objects.</p>
45  * 
46  * <p>setSessionCache(SessionCache s) is an "IOC" wiring point. Pass it
47  * an implementation of the SessionCache interface.</p> 
48  * 
49  * @author Howard Gilbert
50  */
51 public class SessionManager {
52         
53         /*
54          * Sessions can be saved using any Persistance Framework. If a Cache
55          * is created, the following pointer is filled in and we start to 
56          * use it.
57          */
58         private static Logger log = Logger.getLogger(SessionManager.class.getName());
59         
60         private SessionCache cache = null; // By default, use memory cache only
61         
62         private TreeMap sessions = new TreeMap(); // The memory cache of Sessions
63         
64         private static ServiceProviderContext context = ServiceProviderContext.getInstance();
65
66         private static SecureRandom rand = new SecureRandom();
67         private static final String table = "0123456789" +
68                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
69                 "abcdefgjikjlmnopqrstuvwxyz"+
70                 "$@";
71         public String generateKey() {
72             byte[] trash = new byte[16];
73             char[] ctrash = new char[16];
74             do {
75                 rand.nextBytes(trash);
76                 for (int i=0;i<16;i++) {
77                     trash[i]&=0x3f;
78                     ctrash[i]=(char)table.charAt(trash[i]);
79                 }
80             } while (null!=sessions.get(ctrash));
81             return new String(ctrash);
82         }
83         
84         
85         
86         public synchronized Session findSession(String sessionId, String applicationId ) {
87                 Session s = (Session) sessions.get(sessionId);
88                 if (s==null)
89                         return null;
90                 if (null==s.getAuthenticationAssertion())
91                     return null;
92                 if (!applicationId.equals(s.getApplicationId()))
93                         return null;
94                 return s;
95         }
96
97         private synchronized Session findEmptySession(String sessionId) {
98                 Session s = (Session) sessions.get(sessionId);
99                 if (s==null)
100                         return null;
101                 if (null!=s.getAuthenticationAssertion())
102                     return null;
103                 return s;
104         }
105         
106         
107         protected synchronized void add(Session s) {
108                 sessions.put(s.getKey(), s);
109                 if (cache!=null)
110                         cache.add(s);
111         }
112         
113         protected synchronized void update(Session s) {
114                 sessions.put(s.getKey(), s);
115                 if (cache!=null)
116                         cache.update(s);
117         }
118         
119         protected synchronized void remove(Session s) {
120                 sessions.remove(s.getKey());
121                 if (cache!=null)
122                         cache.remove(s);
123         }
124         
125
126         /**
127          * Test for valid Session
128          * 
129          * @param sessionId      typically, the cookie value from client browser
130          * @param applicationId  id of target application asking about session
131          * @param ipaddr         null, or IP address of client
132          * @return
133          */
134         public 
135                         boolean 
136         isValid(
137                         String sessionId,   
138                         String applicationId, 
139                         String ipaddr         
140                         ){
141                 Session session = findSession(sessionId,applicationId);
142                 ServiceProviderConfig.ApplicationInfo application = context.getServiceProviderConfig().getApplication(applicationId);
143                 if (session==null)
144                         return false; // Cookie value did not match cached session
145                 if (application == null)
146                         return false; // ApplicationConfig ID invalid
147                 if (ipaddr!=null && !ipaddr.equals(session.getIpaddr()))
148                         return false; // Client coming from a different machine
149                 // check for timeout
150                 // Note: RPC prefetches attributes here
151                 return true;
152         }
153
154         
155         /**
156          * Store Principal information identified by generated UUID.<br>
157          * Called from Authentication Assertion Consumer [SHIRE]
158          * 
159          * @param applicationId The application for this session
160          * @param ipaddr The client's remote IP address from HTTP
161          * @param entityId The Entity of the AA issuing the authentication
162          * @param assertion Assertion in case one needs more data
163          * @param authentication subset of assertion with handle
164          * @return String (UUID) to go in the browser cookie
165          */
166         public 
167                         String 
168         newSession(
169                         String applicationId, 
170                         String ipaddr,
171                         String entityId,
172                         SAMLAssertion assertion,
173                         SAMLAuthenticationStatement authenticationStatement,
174                         String emptySessionId // may be null
175                         ){
176                 
177                 ServiceProviderConfig config = context.getServiceProviderConfig();
178                 ApplicationInfo appinfo = config.getApplication(applicationId);
179                 Sessions appSessionValues = appinfo.getApplicationConfig().getSessions();
180                 
181                 String sessionId = null;
182                 
183                 Session session;
184                 if (emptySessionId==null) {
185                     session = new Session(generateKey());
186                 } else {
187                     session = findEmptySession(emptySessionId);
188                     if (session==null) {
189                             session = new Session(generateKey());
190                     }
191                 }
192                 session.setApplicationId(applicationId);
193                 session.setIpaddr(ipaddr);
194                 session.setEntityId(entityId);
195                 
196                 session.setAuthenticationAssertion(assertion);
197                 session.setAuthenticationStatement(authenticationStatement);
198                 
199                 session.setLifetime(appSessionValues.getLifetime());
200                 session.setTimeout(appSessionValues.getTimeout());
201                 
202                 sessionId = session.getKey();
203
204                 // This static method finds its unique instance variable
205                 add(session);
206             log.debug("New Session created "+sessionId);
207
208                 return sessionId;
209         }
210         /**
211          * <p>IOC wiring point to plug in an external SessionCache implementation.
212          * </p>
213          * 
214          * @param cache Plugin object implementing the SessionCache interface
215          */
216         public synchronized void 
217         setCache(
218                         SessionCache cache) {
219                 
220             log.info("Enabling Session Cache");
221                 /*
222                  * The following code supports dynamic switching from
223                  * one cache to another if, for example, you decide
224                  * to change databases without restarting Shibboleth.
225                  * Whether this is useful or not is a matter of dispute.
226                  */
227                 if (this.cache!=null) { // replacing an old cache
228                         this.cache.close(); // close it and leave it for GC
229                         return;
230                 }
231                 
232                 this.cache = cache; 
233                 
234                 /*
235                  * Make sure the Cache knows about in memory sessions
236                  * 
237                  * Note: The cache should probably be wired prior to letting
238                  * the Web server process requests, so in almost all cases this
239                  * block will not be neeed. However, we may allow the configuration
240                  * to change dynamically from uncached to cached in the middle
241                  * of a Shibboleth run, and this allows for that possiblity.
242                  */
243                 if (sessions.size()!=0) {
244                         for (Iterator i=sessions.values().iterator();i.hasNext();) {
245                                 Session s = (Session) i.next();
246                                 cache.add(s);
247                         }
248                 }
249                 
250                 /*
251                  * Now load any Sessions in the cache that are not in memory
252                  * (typically after a reboot).
253                  */
254                 cache.reload(sessions);
255         }
256         
257         public static StringBuffer dumpAttributes(Session session) {
258             StringBuffer sb = new StringBuffer();
259         SAMLResponse attributeResponse = session.getAttributeResponse();
260         Iterator assertions = attributeResponse.getAssertions();
261         while (assertions.hasNext()) {
262             SAMLAssertion assertion = (SAMLAssertion) assertions.next();
263             Iterator statements = assertion.getStatements();
264             while (statements.hasNext()) {
265                 SAMLStatement statement = (SAMLStatement) statements.next();
266                 if (statement instanceof SAMLAttributeStatement) {
267                     SAMLAttributeStatement attributeStatement = 
268                         (SAMLAttributeStatement) statement;
269                     
270                     // Foreach Attribute in the AttributeStatement
271                     Iterator attributes = attributeStatement.getAttributes();
272                     while (attributes.hasNext()) {
273                         SAMLAttribute attribute = 
274                             (SAMLAttribute) attributes.next();
275                         String name = attribute.getName();
276                         String namespace = attribute.getNamespace();
277                         Iterator values = attribute.getValues();
278                         while (values.hasNext()){
279                             String val = (String) values.next();
280                             sb.append(name+" "+namespace+" "+val);
281                         }
282                     }
283                 }
284             }
285         }
286             
287             return sb;
288         }
289
290         public static Map /*<String,String>*/
291         mapAttributes(Session session) {
292             Map /*<String,String>*/attributeMap = new HashMap/*<String,String>*/();
293             StringBuffer sb = new StringBuffer();
294         SAMLResponse attributeResponse = session.getAttributeResponse();
295                 if (attributeResponse==null)
296                         return attributeMap;
297         Iterator assertions = attributeResponse.getAssertions();
298         while (assertions.hasNext()) {
299             SAMLAssertion assertion = (SAMLAssertion) assertions.next();
300             Iterator statements = assertion.getStatements();
301             while (statements.hasNext()) {
302                 SAMLStatement statement = (SAMLStatement) statements.next();
303                 if (statement instanceof SAMLAttributeStatement) {
304                     SAMLAttributeStatement attributeStatement = 
305                         (SAMLAttributeStatement) statement;
306                     
307                     // Foreach Attribute in the AttributeStatement
308                     Iterator attributes = attributeStatement.getAttributes();
309                     while (attributes.hasNext()) {
310                         SAMLAttribute attribute = 
311                             (SAMLAttribute) attributes.next();
312                         String name = attribute.getName();
313                         String namespace = attribute.getNamespace();
314                         ArrayList list = new ArrayList();
315                         Iterator values = attribute.getValues();
316                         String val="";
317                         while (values.hasNext()){
318                             val = (String) values.next();
319                             list.add(val);
320                         }
321                         if (list.size()==1)
322                             attributeMap.put(name,val);
323                         else
324                             attributeMap.put(name,list);
325                     }
326                 }
327             }
328         }
329             
330             return attributeMap;
331         }
332         
333         
334 }