rework saml2 authnreq handler to use a requestcontext object. will make future extens...
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AuthenticationRequestBrowserPost.java
1 /*
2  * Copyright [2007] [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 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
18
19 import javax.servlet.ServletRequest;
20 import javax.servlet.ServletResponse;
21 import javax.servlet.http.HttpServletRequest;
22
23 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
24 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
25 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
26
27 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
28
29 import org.apache.log4j.Logger;
30 import org.joda.time.DateTime;
31 import org.opensaml.common.SAMLObject;
32 import org.opensaml.common.binding.BindingException;
33 import org.opensaml.common.binding.decoding.MessageDecoder;
34 import org.opensaml.saml2.core.AuthnRequest;
35 import org.opensaml.saml2.core.Response;
36 import org.opensaml.ws.security.SecurityPolicyException;
37
38 /**
39  * Browser POST binding for SAML 2 AuthenticationRequest.
40  */
41 public class AuthenticationRequestBrowserPost extends AbstractAuthenticationRequest {
42     
43     /** Class logger. */
44     private static final Logger log = Logger.getLogger(AuthenticationRequestBrowserPost.class);
45     
46     /** SAML 2 Profile ID. */
47     protected static final String PROFILE_ID = "urn:oasis:names:tc:SAML:2.0:profiles:SSO:browser";
48     
49     /** SAML 2 Binding URI. */
50     protected static final String BINDING_URI = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
51     
52     /** SubjectConfirmation method for Web Browser SSO profile. */
53     protected static final String SUBJ_CONF_METHOD_URI = "urn:oasis:namurn:oasis:names:tc:SAML:2.0:cm:bearer";
54     
55     /** Constructor. */
56     public AuthenticationRequestBrowserPost() {
57         super();
58     }
59     
60     /** {@inheritDoc} */
61     public String getProfileId() {
62         return PROFILE_ID;
63     }
64     
65     /** {@inheritDoc} */
66     public void processRequest(final ProfileRequest<ServletRequest> request,
67             final ProfileResponse<ServletResponse> response)
68             throws ProfileException {
69         
70         // Only http servlets are supported for now.
71         if (!(request.getRawRequest() instanceof HttpServletRequest)) {
72             log.error("Received a non-HTTP request");
73             throw new ProfileException("Received a non-HTTP request");
74         }
75         
76         // This method is called twice.
77         // On the first time, there will be no AuthenticationRequestContext object. We redirect control to the
78         // AuthenticationManager to authenticate the user. The AuthenticationManager then redirects control
79         // back to this servlet. On the "return leg" connection, there will be a AuthenticationRequestContext object.
80         
81         HttpServletRequest req = (HttpServletRequest) request.getRawRequest();
82         Object o = req.getSession().getAttribute(REQUEST_CONTEXT_SESSION_KEY);
83         if (o != null && !(o instanceof AuthenticationRequestContext)) {
84             log.error("SAML 2 AuthnRequest: Invalid session data found for AuthenticationRequestContext");
85             throw new ProfileException("SAML 2 AuthnRequest: Invalid session data found for AuthenticationRequestContext");
86         }
87         
88         if (o == null) {
89             setupNewRequest(request, response);
90         } else {
91             
92             AuthenticationRequestContext requestContext = (AuthenticationRequestContext)o;
93             
94             // clean up the HttpSession.
95             requestContext.getHttpSession().removeAttribute(REQUEST_CONTEXT_SESSION_KEY);
96             requestContext.getHttpSession().removeAttribute(LoginContext.LOGIN_CONTEXT_KEY);
97             
98             finishProcessingRequest(requestContext);
99         }
100     }
101     
102     /**
103      * Begin processing a SAML 2.0 AuthnRequest.
104      *
105      * This ensures that the request is well-formed and that
106      * appropriate metadata can be found for the SP.
107      * Once these conditions are met, control is passed to
108      * the AuthenticationManager to authenticate the user.
109      * 
110      * @param request The ProfileRequest.
111      * @param response The ProfileResponse
112      * 
113      * @throws ProfileException On error.
114      */
115     protected void setupNewRequest(final ProfileRequest<ServletRequest> request,
116             final ProfileResponse<ServletResponse> response) throws ProfileException {
117         
118         // If the user hasn't been authenticated, validate the AuthnRequest
119         // and redirect to AuthenticationManager to authenticate the user.
120         
121         AuthenticationRequestContext requestContext = new AuthenticationRequestContext();
122         
123         requestContext.setProfileRequest(request);
124         requestContext.setProfileResponse(response);
125         
126         try {
127             // decode the AuthnRequest
128             MessageDecoder<ServletRequest> decoder = getMessageDecoderFactory().getMessageDecoder(BINDING_URI);
129             if (decoder == null) {
130                 log.error("SAML 2 AuthnRequest: No MessageDecoder registered for " + BINDING_URI);
131                 throw new ProfileException("SAML 2 AuthnRequest: No MessageDecoder registered for " + BINDING_URI);
132             }
133             
134             decoder.setMetadataProvider(getMetadataProvider());
135             populateMessageDecoder(decoder);
136             decoder.decode();
137             
138             SAMLObject samlObject = decoder.getSAMLMessage();
139             if (!(samlObject instanceof AuthnRequest)) {
140                 log.error("SAML 2 AuthnRequest: Received message is not a SAML 2 Authentication Request");
141                 throw new ProfileException("SAML 2 AuthnRequest: Received message is not a SAML 2 Authentication Request");
142             }
143             
144             requestContext.setAuthnRequest((AuthnRequest) samlObject);
145             requestContext.setIssuer(decoder.getSecurityPolicy().getIssuer());
146             validateRequestAgainstMetadata(requestContext);
147             verifyAuthnRequest(requestContext);
148             authenticateUser(requestContext);
149             
150         } catch (BindingException ex) {
151             log.error("SAML 2 Authentication Request: Unable to decode SAML 2 Authentication Request", ex);
152             throw new ProfileException(
153                     "SAML 2 Authentication Request: Unable to decode SAML 2 Authentication Request", ex);
154         } catch (SecurityPolicyException ex) {
155             log.error("SAML 2 Authentication Request: Security error while decoding SAML 2 Authentication Request", ex);
156         } catch (AuthenticationRequestException ex) {
157             
158             // AuthN failed. Send the failure status.
159             requestContext.setResponse(buildResponse(requestContext.getAuthnRequest().getID(), 
160                     new DateTime(), requestContext.getIssuer(), ex.getStatus()));
161             encodeResponse(BINDING_URI, requestContext);
162         }
163     }
164     
165     /**
166      * Process the "return leg" of a SAML 2 Authentication Request.
167      *
168      * This evaluates the AuthenticationManager's LoginContext and generates an Authentication Assertion.
169      *
170      * @param requestContext The context for this request.
171      *
172      * @throws ProfileException On error.
173      */
174     protected void finishProcessingRequest(final AuthenticationRequestContext requestContext) throws ProfileException {
175         
176         // The user has already been authenticated,
177         // so generate an AuthenticationStatement.
178         evaluateRequest(requestContext);
179         encodeResponse(BINDING_URI, requestContext);
180     }
181     
182 }