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