Now with SAML 1 attribute query goodness (though not yet tested)
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml1 / AttributeQueryProfileHandler.java
1 /*
2  * Copyright [2006] [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.saml1;
18
19 import java.util.ArrayList;
20 import java.util.Map;
21
22 import javax.servlet.ServletRequest;
23 import javax.servlet.ServletResponse;
24
25 import org.apache.log4j.Logger;
26 import org.opensaml.common.binding.BindingException;
27 import org.opensaml.common.binding.decoding.MessageDecoder;
28 import org.opensaml.common.binding.encoding.MessageEncoder;
29 import org.opensaml.common.binding.security.SAMLSecurityPolicy;
30 import org.opensaml.saml1.binding.decoding.HTTPSOAP11Decoder;
31 import org.opensaml.saml1.binding.encoding.HTTPSOAP11Encoder;
32 import org.opensaml.saml1.core.AttributeQuery;
33 import org.opensaml.saml1.core.AttributeStatement;
34 import org.opensaml.saml1.core.NameIdentifier;
35 import org.opensaml.saml1.core.Response;
36 import org.opensaml.saml1.core.Statement;
37 import org.opensaml.saml1.core.StatusCode;
38 import org.opensaml.saml1.core.Subject;
39 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
40 import org.opensaml.saml2.metadata.SPSSODescriptor;
41 import org.opensaml.ws.security.SecurityPolicyException;
42
43 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
44 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
45 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority;
46 import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethSAMLAttributeRequestContext;
47 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
48 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
49 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
50 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
51 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml1.AttributeQueryConfiguration;
52 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
53 import edu.internet2.middleware.shibboleth.idp.session.Session;
54
55 /**
56  * SAML 1 Attribute Query profile handler.
57  */
58 public class AttributeQueryProfileHandler extends AbstractSAML1ProfileHandler {
59
60     /** Class logger. */
61     private final Logger log = Logger.getLogger(AttributeQueryProfileHandler.class);
62
63     /** {@inheritDoc} */
64     public String getProfileId() {
65         return "urn:mace:shibboleth:2.0:idp:profiles:saml1:query:attribute";
66     }
67
68     /** {@inheritDoc} */
69     public void processRequest(ProfileRequest<ServletRequest> request, ProfileResponse<ServletResponse> response)
70             throws ProfileException {
71
72         AttributeQueryContext requestContext = new AttributeQueryContext(request, response);
73
74         Response samlResponse;
75         try {
76             decodeRequest(requestContext);
77
78             ArrayList<Statement> statements = new ArrayList<Statement>();
79             statements.add(buildAttributeStatement(requestContext));
80
81             samlResponse = buildResponse(requestContext, statements);
82         } catch (SecurityPolicyException e) {
83             samlResponse = buildErrorResponse(requestContext, StatusCode.REQUESTER, StatusCode.REQUEST_DENIED, e
84                     .getMessage());
85         } catch (AttributeRequestException e) {
86             samlResponse = buildErrorResponse(requestContext, StatusCode.RESPONDER, null, e.getMessage());
87         } catch (ProfileException e) {
88             samlResponse = buildErrorResponse(requestContext, StatusCode.RESPONDER, StatusCode.REQUEST_DENIED, e
89                     .getMessage());
90         }
91     }
92
93     /**
94      * Decodes the message in the request and adds it to the request context.
95      * 
96      * @param requestContext request context contianing the request to decode
97      * 
98      * @throws ProfileException throw if there is a problem decoding the request
99      * @throws SecurityPolicyException thrown if the message was decoded properly but did not meet the necessary
100      *             security policy requirements
101      */
102     protected void decodeRequest(AttributeQueryContext requestContext) throws ProfileException, SecurityPolicyException {
103         if (log.isDebugEnabled()) {
104             log.debug("Decoding incomming request");
105         }
106         MessageDecoder<ServletRequest> decoder = getMessageDecoderFactory().getMessageDecoder(
107                 HTTPSOAP11Decoder.BINDING_URI);
108         if (decoder == null) {
109             throw new ProfileException("No request decoder was registered for binding type: "
110                     + HTTPSOAP11Decoder.BINDING_URI);
111         }
112         super.populateMessageDecoder(decoder);
113
114         decoder.setRequest(requestContext.getProfileRequest().getRawRequest());
115         requestContext.setMessageDecoder(decoder);
116
117         try {
118             decoder.decode();
119             if (log.isDebugEnabled()) {
120                 log.debug("Decoded request");
121             }
122         } catch (BindingException e) {
123             log.error("Error decoding attribute query message", e);
124             throw new ProfileException("Error decoding attribute query message");
125         } finally {
126             // Set as much information as can be retrieved from the decoded message
127             SAMLSecurityPolicy securityPolicy = requestContext.getMessageDecoder().getSecurityPolicy();
128             requestContext.setRelyingPartyId(securityPolicy.getIssuer());
129
130             RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(requestContext.getRelyingPartyId());
131             requestContext.setRelyingPartyConfiguration(rpConfig);
132
133             requestContext.setRelyingPartyRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
134
135             requestContext.setAssertingPartyId(requestContext.getRelyingPartyConfiguration().getProviderId());
136
137             requestContext.setAssertingPartyRole(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
138
139             requestContext.setProfileConfiguration((AttributeQueryConfiguration) rpConfig
140                     .getProfileConfiguration(AttributeQueryConfiguration.PROFILE_ID));
141
142             requestContext.setSamlRequest((AttributeQuery) requestContext.getMessageDecoder().getSAMLMessage());
143         }
144     }
145
146     /**
147      * Executes a query for attributes and builds a SAML attribute statement from the results.
148      * 
149      * @param requestContext current request context
150      * 
151      * @return attribute statement resulting from the query
152      * 
153      * @throws ProfileException thrown if there is a problem making the query
154      * @throws AttributeRequestException thrown if there is a problem resolving attributes
155      */
156     protected AttributeStatement buildAttributeStatement(AttributeQueryContext requestContext) throws ProfileException,
157             AttributeRequestException {
158
159         if (log.isDebugEnabled()) {
160             log.debug("Creating attribute statement in response to SAML request  from relying party "
161                     + requestContext.getRelyingPartyId());
162         }
163
164         try {
165             AttributeQueryConfiguration profileConfiguration = requestContext.getProfileConfiguration();
166             if (profileConfiguration == null) {
167                 log.error("No SAML 1 attribute query profile configuration is defined for relying party: "
168                         + requestContext.getRelyingPartyId());
169                 throw new AttributeRequestException("SAML 1 attribute query is not configured for this relying party");
170             }
171
172             SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
173
174             ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery> attributeRequestContext = buildAttributeRequestContext(requestContext);
175
176             if (log.isDebugEnabled()) {
177                 log.debug("Resolving principal name for subject of SAML request from relying party "
178                         + requestContext.getRelyingPartyId());
179             }
180             String principal = attributeAuthority.getPrincipal(attributeRequestContext);
181             requestContext.setPrincipalName(principal);
182
183             if (log.isDebugEnabled()) {
184                 log.debug("Resolving attributes for principal " + principal + " of SAML request from relying party "
185                         + requestContext.getRelyingPartyId());
186             }
187             Map<String, BaseAttribute> principalAttributes = attributeAuthority
188                     .getAttributes(buildAttributeRequestContext(requestContext));
189
190             requestContext.setPrincipalAttributes(principalAttributes);
191
192             AttributeStatement statment = attributeAuthority.buildAttributeStatement(requestContext.getSamlRequest(),
193                     principalAttributes.values());
194
195             Subject statementSubject = buildSubject(requestContext, "urn:oasis:names:tc:SAML:1.0:cm:sender-vouches");
196             statment.setSubject(statementSubject);
197
198             return statment;
199         } catch (AttributeRequestException e) {
200             log.error("Error resolving attributes for SAML request from relying party "
201                     + requestContext.getRelyingPartyId(), e);
202             throw e;
203         }
204     }
205
206     /**
207      * Creates an attribute query context from the current profile request context.
208      * 
209      * @param requestContext current profile request
210      * 
211      * @return created query context
212      */
213     protected ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery> buildAttributeRequestContext(
214             AttributeQueryContext requestContext) {
215
216         ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery> queryContext = new ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery>(
217                 getMetadataProvider(), requestContext.getRelyingPartyConfiguration(), requestContext.getSamlRequest());
218
219         queryContext.setAttributeRequester(requestContext.getAssertingPartyId());
220         queryContext.setPrincipalName(requestContext.getPrincipalName());
221         queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
222         queryContext.setRequest(requestContext.getProfileRequest());
223
224         Session userSession = getSessionManager().getSession(getUserSessionId(requestContext.getProfileRequest()));
225         if (userSession != null) {
226             queryContext.setUserSession(userSession);
227             ServiceInformation serviceInfo = userSession.getServiceInformation(requestContext.getRelyingPartyId());
228             if (serviceInfo != null) {
229                 String principalAuthenticationMethod = serviceInfo.getAuthenticationMethod().getAuthenticationMethod();
230
231                 requestContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
232                 queryContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
233             }
234         }
235
236         return queryContext;
237     }
238
239     /**
240      * Encodes the request's SAML response and writes it to the servlet response.
241      * 
242      * @param requestContext current request context
243      * 
244      * @throws ProfileException thrown if no message encoder is registered for this profiles binding
245      */
246     protected void encodeResponse(AttributeQueryContext requestContext) throws ProfileException {
247         if (log.isDebugEnabled()) {
248             log.debug("Encoding response to SAML request from relying party " + requestContext.getRelyingPartyId());
249         }
250         MessageEncoder<ServletResponse> encoder = getMessageEncoderFactory().getMessageEncoder(
251                 HTTPSOAP11Encoder.BINDING_URI);
252         if (encoder == null) {
253             throw new ProfileException("No response encoder was registered for binding type: "
254                     + HTTPSOAP11Encoder.BINDING_URI);
255         }
256
257         super.populateMessageEncoder(encoder);
258         encoder.setResponse(requestContext.getProfileResponse().getRawResponse());
259         encoder.setSamlMessage(requestContext.getSamlResponse());
260         requestContext.setMessageEncoder(encoder);
261
262         try {
263             encoder.encode();
264         } catch (BindingException e) {
265             throw new ProfileException("Unable to encode response to relying party: "
266                     + requestContext.getRelyingPartyId(), e);
267         }
268     }
269
270     /** Basic data structure used to accumulate information as a request is being processed. */
271     protected class AttributeQueryContext extends
272             SAML1ProfileRequestContext<AttributeQuery, Response, AttributeQueryConfiguration> {
273
274         /**
275          * Constructor.
276          * 
277          * @param request current profile request
278          * @param response current profile response
279          */
280         public AttributeQueryContext(ProfileRequest<ServletRequest> request, ProfileResponse<ServletResponse> response) {
281             super(request, response);
282         }
283     }
284 }