Extra Audiences
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / AssertionConsumerServlet.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  * AuthenticatonAssertionConsumerServlet
19  * 
20  * The Shibboleth function previous known as the SHIRE.
21  * 
22  * Authentication Assertion Consumer is the SAML 2.0 term for what the
23  * SHIRE does. A SAML Assertion containing an Authentication statement
24  * with the "principal" identifier value equal to the handle vended by
25  * the Handle Server is received from the Browser. The Handle Server
26  * generated a form, prefilled it with a Bin64 encoding of the SAML
27  * statement, and included Javascript to automatically submit the form
28  * to this URL.
29  * 
30  * All HTTP, HTML, and servlet logic is localized to this layer of
31  * modules. Any information must be extracted from the Servlet API
32  * to be passed directly to Shibboleth.
33  * 
34  * The work is done by a ShibPOSTProfile object. It takes the Bin64
35  * encoded string, converts it to a SAMLObject, verifies structure,
36  * and validates signatures.
37  * 
38  * The resulting Authentication Assertion SAML statement is passed
39  * to Session Manager to create a new session. This process feeds
40  * back a session identifier that becomes the value of a Cookie sent
41  * back to the Browser to track the session.
42  * 
43  * If the decision is made to fetch attributes immediately, the 
44  * Session object can be passed to the static AttributeRequestor
45  * service. It generates a ShibBinding, sends a request to the AA,
46  * validates the response, applies AAP, and stores the resulting 
47  * SAML Attribute Assertion in the Session object.
48  */
49 package edu.internet2.middleware.shibboleth.serviceprovider;
50
51 import java.io.IOException;
52 import java.util.Iterator;
53
54 import javax.servlet.ServletContext;
55 import javax.servlet.ServletException;
56 import javax.servlet.ServletOutputStream;
57 import javax.servlet.http.Cookie;
58 import javax.servlet.http.HttpServlet;
59 import javax.servlet.http.HttpServletRequest;
60 import javax.servlet.http.HttpServletResponse;
61
62 import org.apache.log4j.Logger;
63 import org.opensaml.SAMLAudienceRestrictionCondition;
64 import org.opensaml.SAMLCondition;
65 import org.opensaml.SAMLException;
66 import org.opensaml.SAMLResponse;
67 import org.opensaml.SAMLBrowserProfile.BrowserProfileResponse;
68
69 import x0.maceShibbolethTargetConfig1.ApplicationDocument.Application;
70 import x0.maceShibbolethTargetConfig1.SessionsDocument.Sessions;
71 import edu.internet2.middleware.shibboleth.common.ShibBrowserProfile;
72 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
73 import edu.internet2.middleware.shibboleth.resource.AuthenticationFilter;
74 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
75
76 /**
77  * Process the Authentication Assertion POST data to create a Session.
78  * 
79  * @author Howard Gilbert
80  */
81 public class AssertionConsumerServlet extends HttpServlet {
82
83         private static final String SESSIONCOOKIE = "ShibbolethSPSession";
84
85         private static Logger log = Logger.getLogger(AssertionConsumerServlet.class.getName());
86         
87         private static ServiceProviderContext context = ServiceProviderContext.getInstance();
88         
89         public static final String SESSIONPARM =
90             "ShibbolethSessionId";
91         
92         
93         public void init() throws ServletException {
94                 super.init();
95                 ServletContext servletContext = this.getServletContext();
96                 
97                 // Note: the ServletContext should have been initialized by the Listener
98                 ServletContextInitializer.initServiceProvider(servletContext);
99                 
100                 // Establish linkage between the SP context and the RM Filter class
101                 AuthenticationFilter.setFilterSupport(new FilterSupportImpl());
102         }
103
104
105
106         /**
107          * Accept the SAML Assertion post from the HS.
108          * 
109          * @param request the request send by the client to the server
110          * @param response the response send by the server to the client
111          */
112         public void doPost(
113                 HttpServletRequest request,
114                 HttpServletResponse response)
115                 {
116             ServletContextInitializer.beginService(request,response);
117                 try {
118             ServiceProviderConfig config = context.getServiceProviderConfig();
119             
120             String ipaddr = request.getRemoteAddr();
121             
122             // URL of Resource that triggered authorization
123             // I added support to the profile for extracting TARGET, but
124             // it's not too critical in Java since you can grab it easily anyway.
125             // Might be better in the 2.0 future though, since the bindings get trickier.
126             String target = request.getParameter("TARGET");
127             
128             // Map the Resource URL into an <Application>
129             String applicationId = config.mapRequest(target);
130             ApplicationInfo appinfo = config.getApplication(applicationId);
131             Sessions appSessionValues = appinfo.getApplicationConfig().getSessions();
132             String shireURL = request.getRequestURL().toString();
133             String providerId = appinfo.getApplicationConfig().getProviderId();
134             
135            
136             if (appSessionValues.getShireSSL()&& // Requires SSL
137                         !request.isSecure()) {       // isn't SSL
138                 log.error("Authentication Assertion not posted over SSL.");
139                 try {
140                     response.sendRedirect("shireError.html");
141                 } catch (IOException e1) {
142                 }
143                 return;
144             }
145             
146             log.debug("Authentication received from "+ipaddr+" for "+target+
147                         "(application:"+applicationId+") (Provider:"+providerId+")");
148
149             String sessionId = createSessionFromPost(ipaddr, request, applicationId, shireURL, providerId, null);
150             
151             Cookie cookie = new Cookie(SESSIONCOOKIE,sessionId);
152             response.addCookie(cookie);
153             
154             /**
155              * This is included as a diagnostic, although it was suggested by a user
156              * as a future API for someone holding an Authentication and wishing to
157              * know what it means.
158              */
159             try {
160                                 if (target.equals("SendAttributesBackToMe")) {
161                                         ServletOutputStream outputStream = response.getOutputStream();
162                                         response.setContentType("text/xml");
163                                         Session session = context.getSessionManager().findSession(sessionId,applicationId);
164                                         SAMLResponse attributeResponse = session.getAttributeResponse();
165                                         outputStream.print(attributeResponse.toString());
166                                 } else {
167                                         response.sendRedirect(target+"?"+SESSIONPARM+"="+sessionId);
168                                 }
169             }
170             catch (IOException e) {
171             }
172         }
173         catch (MetadataException e) {
174             log.error("Authentication Assertion source not found in Metadata.");
175             try {
176                 response.sendRedirect("shireError.html");
177             }
178             catch (IOException e1) {
179             }
180         }
181         catch (SAMLException e) {
182             log.error("Authentication Assertion had invalid format.");
183             try {
184                 response.sendRedirect("shireError.html");
185             }
186             catch (IOException e1) {
187             }
188         }
189         finally {
190             ServletContextInitializer.finishService(request,response);
191         }
192         }
193         
194     /**
195      * Create a Session object from SHIRE POST data
196      * 
197      * <p>Used within this class to handle POSTs to the SP itself, but
198      * also by the FilterSupport logic when the POST is to the RM 
199      * context.</p>
200      * 
201      * @param ipaddr IP Address of Browser
202      * @param bin64Assertion Authentication assertion from POST
203      * @param applicationId from RequestMap
204      * @param shireURL 
205      * @param providerId Our Entity name
206      * @return random key of Session
207      * @throws SAMLException
208      */
209     public static 
210     String createSessionFromPost(
211             String ipaddr, 
212             HttpServletRequest req, 
213             String applicationId, 
214             String shireURL, 
215             String providerId,
216             String emptySessionId
217             ) 
218     throws SAMLException {
219         String sessionid=null;
220         StringBuffer pproviderId = // Get back IdP Entity name from SAML
221             new StringBuffer();
222         
223         ShibBrowserProfile profile = new ShibBrowserProfile(applicationId);
224         BrowserProfileResponse samldata = profile.receive(
225                 pproviderId,
226                 req,
227                 shireURL,   // My URL (Why??) To prevent attackers from redirecting messages. 
228                 context.getReplayCache(),
229                 null,
230                 1
231         );
232         
233         ServiceProviderConfig config = context.getServiceProviderConfig();
234         ApplicationInfo application = config.getApplication(applicationId);
235         Application applicationConfig = application.getApplicationConfig();
236         String[] audienceArray = applicationConfig.getAudienceArray();
237         
238         
239         Iterator conditions = samldata.assertion.getConditions();
240         while (conditions.hasNext()) {
241             SAMLCondition cond =
242                 (SAMLCondition)conditions.next();
243             
244             if (cond instanceof SAMLAudienceRestrictionCondition) {
245                 SAMLAudienceRestrictionCondition audienceCondition =
246                     (SAMLAudienceRestrictionCondition) cond;
247                 Iterator audiences = audienceCondition.getAudiences();
248                 if (audiences==null)
249                     continue; // probably invalid
250                 boolean matched = false;
251                 StringBuffer audienceTests = new StringBuffer();
252                 while (!matched && audiences.hasNext()) {
253                     String audienceString = (String) audiences.next();
254                     audienceTests.append(audienceString);
255                     audienceTests.append(' ');
256                     if (audienceString.equals(providerId)) {
257                         matched=true;
258                     }
259                     if (audienceArray!=null) {
260                         for (int i=0;i<audienceArray.length;i++) {
261                             if (audienceString.equals(audienceArray[i])) {
262                                 matched=true;
263                                 break;
264                             }
265                         }
266                     }
267                 }
268                 if (!matched) {
269                     log.error("Assertion restricted to "+audienceTests.toString());
270                     StringBuffer audienceBuffer = new StringBuffer("Did not match ");
271                     audienceBuffer.append(providerId);
272                     if (audienceArray!=null && audienceArray.length>0) {
273                         audienceBuffer.append(" or ");
274                         for (int i=0;i<audienceArray.length;i++) {
275                             audienceBuffer.append(audienceArray[i]);
276                             audienceBuffer.append(' ');
277                         }
278                     }
279                     log.error(audienceBuffer.toString());
280                     throw new SAMLException("Assertion failed audience restriction test.");
281                 }
282             }
283         }
284         
285         // The Authentication Assertion gets placed in a newly created
286         // Session object. Later, someone will get an Attribute Assertion
287         // and add it to the Session. The SessionID key is returned to
288         // the Browser as a Cookie.
289         SessionManager sessionManager = context.getSessionManager();
290         sessionid = sessionManager.newSession(
291                 applicationId, 
292                 ipaddr, 
293                 pproviderId.toString(), 
294                 samldata.assertion, 
295                 samldata.authnStatement,
296                 emptySessionId);
297         
298         // Very agressive attribute fetch rule 
299         // Get the Attributes immediately! [good for debugging]
300         Session session = sessionManager.findSession(sessionid, applicationId);
301         AttributeRequestor.fetchAttributes(session);
302
303         return sessionid;
304     }
305
306
307
308     protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1)
309         throws ServletException, IOException {
310         // Currently the Assertion Consumer does not receive a GET
311     }
312         
313
314 }