Recognize Attribute Push
[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.SAMLAssertion;
64 import org.opensaml.SAMLAttributeStatement;
65 import org.opensaml.SAMLAudienceRestrictionCondition;
66 import org.opensaml.SAMLCondition;
67 import org.opensaml.SAMLException;
68 import org.opensaml.SAMLResponse;
69 import org.opensaml.SAMLStatement;
70 import org.opensaml.SAMLBrowserProfile.BrowserProfileResponse;
71
72 import x0.maceShibbolethTargetConfig1.ApplicationDocument.Application;
73 import x0.maceShibbolethTargetConfig1.SessionsDocument.Sessions;
74 import edu.internet2.middleware.shibboleth.common.ShibBrowserProfile;
75 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
76 import edu.internet2.middleware.shibboleth.resource.AuthenticationFilter;
77 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
78
79 /**
80  * Process the Authentication Assertion POST data to create a Session.
81  * 
82  * @author Howard Gilbert
83  */
84 public class AssertionConsumerServlet extends HttpServlet {
85
86         private static final String SESSIONCOOKIE = "ShibbolethSPSession";
87
88         private static Logger log = Logger.getLogger(AssertionConsumerServlet.class.getName());
89         
90         private static ServiceProviderContext context = ServiceProviderContext.getInstance();
91         
92         public static final String SESSIONPARM =
93             "ShibbolethSessionId";
94         
95         
96         public void init() throws ServletException {
97                 super.init();
98                 ServletContext servletContext = this.getServletContext();
99                 
100                 // Note: the ServletContext should have been initialized by the Listener
101                 ServletContextInitializer.initServiceProvider(servletContext);
102                 
103                 // Establish linkage between the SP context and the RM Filter class
104                 AuthenticationFilter.setFilterSupport(new FilterSupportImpl());
105         }
106
107
108
109         /**
110          * Accept the SAML Assertion post from the HS.
111          * 
112          * @param request the request send by the client to the server
113          * @param response the response send by the server to the client
114          */
115         public void doPost(
116                 HttpServletRequest request,
117                 HttpServletResponse response)
118                 {
119             ServletContextInitializer.beginService(request,response);
120                 try {
121             ServiceProviderConfig config = context.getServiceProviderConfig();
122             
123             String ipaddr = request.getRemoteAddr();
124             
125             // URL of Resource that triggered authorization
126             // I added support to the profile for extracting TARGET, but
127             // it's not too critical in Java since you can grab it easily anyway.
128             // Might be better in the 2.0 future though, since the bindings get trickier.
129             String target = request.getParameter("TARGET");
130             
131             // Map the Resource URL into an <Application>
132             String applicationId = config.mapRequest(target);
133             ApplicationInfo appinfo = config.getApplication(applicationId);
134             Sessions appSessionValues = appinfo.getApplicationConfig().getSessions();
135             String shireURL = request.getRequestURL().toString();
136             String providerId = appinfo.getApplicationConfig().getProviderId();
137             
138            
139             if (appSessionValues.getShireSSL()&& // Requires SSL
140                         !request.isSecure()) {       // isn't SSL
141                 log.error("Authentication Assertion not posted over SSL.");
142                 try {
143                     response.sendRedirect("shireError.html");
144                 } catch (IOException e1) {
145                 }
146                 return;
147             }
148             
149             log.debug("Authentication received from "+ipaddr+" for "+target+
150                         "(application:"+applicationId+") (Provider:"+providerId+")");
151
152             String sessionId = createSessionFromPost(ipaddr, request, applicationId, shireURL, providerId, null);
153             
154             Cookie cookie = new Cookie(SESSIONCOOKIE,sessionId);
155             response.addCookie(cookie);
156             
157             /**
158              * This is included as a diagnostic, although it was suggested by a user
159              * as a future API for someone holding an Authentication and wishing to
160              * know what it means.
161              */
162             try {
163                                 if (target.equals("SendAttributesBackToMe")) {
164                                         ServletOutputStream outputStream = response.getOutputStream();
165                                         response.setContentType("text/xml");
166                                         Session session = context.getSessionManager().findSession(sessionId,applicationId);
167                                         SAMLResponse attributeResponse = session.getAttributeResponse();
168                                         outputStream.print(attributeResponse.toString());
169                                 } else {
170                                         response.sendRedirect(target+"?"+SESSIONPARM+"="+sessionId);
171                                 }
172             }
173             catch (IOException e) {
174             }
175         }
176         catch (MetadataException e) {
177             log.error("Authentication Assertion source not found in Metadata.");
178             try {
179                 response.sendRedirect("shireError.html");
180             }
181             catch (IOException e1) {
182             }
183         }
184         catch (SAMLException e) {
185             log.error("Authentication Assertion had invalid format.");
186             try {
187                 response.sendRedirect("shireError.html");
188             }
189             catch (IOException e1) {
190             }
191         }
192         finally {
193             ServletContextInitializer.finishService(request,response);
194         }
195         }
196         
197     /**
198      * Create a Session object from SHIRE POST data
199      * 
200      * <p>Used within this class to handle POSTs to the SP itself, but
201      * also by the FilterSupport logic when the POST is to the RM 
202      * context.</p>
203      * 
204      * @param ipaddr IP Address of Browser
205      * @param bin64Assertion Authentication assertion from POST
206      * @param applicationId from RequestMap
207      * @param shireURL 
208      * @param providerId Our Entity name
209      * @return random key of Session
210      * @throws SAMLException
211      */
212     public static 
213     String createSessionFromPost(
214             String ipaddr, 
215             HttpServletRequest req, 
216             String applicationId, 
217             String shireURL, 
218             String providerId,
219             String emptySessionId
220             ) 
221     throws SAMLException {
222         String sessionid=null;
223         StringBuffer pproviderId = // Get back IdP Entity name from SAML
224             new StringBuffer();
225         ServiceProviderConfig config = context.getServiceProviderConfig();
226         ApplicationInfo application = config.getApplication(applicationId);
227         Application applicationConfig = application.getApplicationConfig();
228         
229         ShibBrowserProfile profile = new ShibBrowserProfile(applicationId);
230         SPArtifactMapper mapper = new SPArtifactMapper(application,config);
231         BrowserProfileResponse samldata = profile.receive(
232                 pproviderId,
233                 req,
234                 shireURL,   // My URL (Why??) To prevent attackers from redirecting messages. 
235                 context.getReplayCache(),
236                 mapper,
237                 1
238         );
239         
240         String[] audienceArray = applicationConfig.getAudienceArray();
241         
242         
243         Iterator conditions = samldata.assertion.getConditions();
244         while (conditions.hasNext()) {
245             SAMLCondition cond =
246                 (SAMLCondition)conditions.next();
247             
248             if (cond instanceof SAMLAudienceRestrictionCondition) {
249                 SAMLAudienceRestrictionCondition audienceCondition =
250                     (SAMLAudienceRestrictionCondition) cond;
251                 Iterator audiences = audienceCondition.getAudiences();
252                 if (audiences==null)
253                     continue; // probably invalid
254                 boolean matched = false;
255                 StringBuffer audienceTests = new StringBuffer();
256                 while (!matched && audiences.hasNext()) {
257                     String audienceString = (String) audiences.next();
258                     audienceTests.append(audienceString);
259                     audienceTests.append(' ');
260                     if (audienceString.equals(providerId)) {
261                         matched=true;
262                     }
263                     if (audienceArray!=null) {
264                         for (int i=0;i<audienceArray.length;i++) {
265                             if (audienceString.equals(audienceArray[i])) {
266                                 matched=true;
267                                 break;
268                             }
269                         }
270                     }
271                 }
272                 if (!matched) {
273                     log.error("Assertion restricted to "+audienceTests.toString());
274                     StringBuffer audienceBuffer = new StringBuffer("Did not match ");
275                     audienceBuffer.append(providerId);
276                     if (audienceArray!=null && audienceArray.length>0) {
277                         audienceBuffer.append(" or ");
278                         for (int i=0;i<audienceArray.length;i++) {
279                             audienceBuffer.append(audienceArray[i]);
280                             audienceBuffer.append(' ');
281                         }
282                     }
283                     log.error(audienceBuffer.toString());
284                     throw new SAMLException("Assertion failed audience restriction test.");
285                 }
286             }
287         }
288         
289         // The Authentication Assertion gets placed in a newly created
290         // Session object. Later, someone will get an Attribute Assertion
291         // and add it to the Session. The SessionID key is returned to
292         // the Browser as a Cookie.
293         SessionManager sessionManager = context.getSessionManager();
294         sessionid = sessionManager.newSession(
295                 applicationId, 
296                 ipaddr, 
297                 pproviderId.toString(), 
298                 samldata.assertion, 
299                 samldata.authnStatement,
300                 emptySessionId);
301         
302         // Very agressive attribute fetch rule 
303         // Get the Attributes immediately! [good for debugging]
304         Session session = sessionManager.findSession(sessionid, applicationId);
305         
306         checkForAttributePush(samldata, session);
307         
308         AttributeRequestor.fetchAttributes(session);
309
310         return sessionid;
311     }
312
313
314     /**
315      * Scan the POST data for Attribute Assertions. If any are found,
316      * then attributes have been pushed and we don't need to go to 
317      * the AA to get them. 
318      * @param samldata The BrowserProfileResponse containing the SAMLResponse
319      * @param session The Session just created
320      */
321     private static void checkForAttributePush(BrowserProfileResponse samldata, Session session) {
322         SAMLResponse samlresponse = samldata.response;
323         Iterator assertions = samlresponse.getAssertions();
324         while (assertions.hasNext()) {
325             SAMLAssertion assertion = (SAMLAssertion) assertions.next();
326             Iterator statements = assertion.getStatements();
327             while (statements.hasNext()) {
328                 SAMLStatement statement = (SAMLStatement) statements.next();
329                 if (statement instanceof SAMLAttributeStatement) {
330                     log.info("Found Attributes with Authenticaiton data (Attribute Push).");
331                     session.setAttributeResponse(samlresponse);
332                     // Note, the Attribute Statements have not been checked for 
333                     // AAP or Signatures. AttributeRequestor will bypass calling
334                     // the AA and will reprocess the POST Response for Attributes.
335                     return;
336                 }
337             }
338         }
339     }
340
341
342     /**
343      * Artifact comes as a GET
344      */
345     protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1)
346         throws ServletException, IOException {
347         log.debug("Received GET: "+ arg0.getQueryString());
348         doPost(arg0,arg1);
349     }
350         
351
352 }