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