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