More work on profile handlers, still have some more refactoring to do
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AbstractAttributeQuery.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.saml2;
18
19 import javax.servlet.ServletRequest;
20 import javax.servlet.ServletResponse;
21
22 import org.apache.log4j.Logger;
23 import org.joda.time.DateTime;
24 import org.opensaml.common.binding.BindingException;
25 import org.opensaml.common.binding.decoding.MessageDecoder;
26 import org.opensaml.common.binding.encoding.MessageEncoder;
27 import org.opensaml.log.Level;
28 import org.opensaml.saml2.core.Assertion;
29 import org.opensaml.saml2.core.AttributeQuery;
30 import org.opensaml.saml2.core.AttributeStatement;
31 import org.opensaml.saml2.core.Response;
32 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
33
34 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
35 import edu.internet2.middleware.shibboleth.common.attribute.SAML2AttributeAuthority;
36 import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethAttributeRequestContext;
37 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
38 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
39 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
40 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
41 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
42 import edu.internet2.middleware.shibboleth.common.relyingparty.saml2.AttributeQueryConfiguration;
43 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
44 import edu.internet2.middleware.shibboleth.idp.session.Session;
45
46 /**
47  * SAML 2.0 Attribute Query profile handler.
48  */
49 public abstract class AbstractAttributeQuery extends AbstractSAML2ProfileHandler {
50
51     /** Class logger. */
52     private static Logger log = Logger.getLogger(AbstractAttributeQuery.class);
53
54     /** {@inheritDoc} */
55     public String getProfileId() {
56         return "urn:oasis:names:tc:SAML:2.0:profiles:query";
57     }
58
59     /** {@inheritDoc} */
60     public void processRequest(ProfileRequest<ServletRequest> request, ProfileResponse<ServletResponse> response)
61             throws ProfileException {
62
63         AttributeQueryRequestContext requestContext = new AttributeQueryRequestContext(request, response);
64
65         getMessageDecoder(requestContext);
66         
67         decodeRequest(requestContext);
68
69         buildResponse(requestContext);
70
71         getMessageEncoder(requestContext);
72
73         try {
74             requestContext.getMessageEncoder().encode();
75             writeAuditLogEntry(requestContext);
76         } catch (BindingException e) {
77             log.error("Unable to encode response the relying party: " + requestContext.getRelyingPartyId(), e);
78             throw new ProfileException("Unable to encode response the relying party: "
79                     + requestContext.getRelyingPartyId(), e);
80         }
81     }
82
83     /**
84      * Gets a populated message decoder.
85      * 
86      * @param requestContext current request context
87      * 
88      * @throws ProfileException thrown if there is no message decoder that may be used to decoder the incoming request
89      */
90     protected abstract void getMessageDecoder(AttributeQueryRequestContext requestContext) throws ProfileException;
91
92     /**
93      * Gets a populated message encoder.
94      * 
95      * @param requestContext current request context
96      * 
97      * @throws ProfileException thrown if there is no message encoder that may be used to encoder the outgoing response
98      */
99     protected abstract void getMessageEncoder(AttributeQueryRequestContext requestContext) throws ProfileException;
100
101     /**
102      * Decodes the message in the request and adds it to the request context.
103      * 
104      * @param requestContext request context contianing the request to decode
105      * 
106      * @throws ProfileException throw if there is a problem decoding the request
107      */
108     protected void decodeRequest(AttributeQueryRequestContext requestContext)
109             throws ProfileException {
110
111         try {
112             requestContext.getMessageDecoder().decode();
113             if (log.isDebugEnabled()) {
114                 log.debug("decoded http servlet request");
115             }
116             requestContext.setAttributeQuery((AttributeQuery) requestContext.getMessageDecoder().getSAMLMessage());
117         } catch (BindingException e) {
118             log.error("Error decoding attribute query message", e);
119             throw new ProfileException("Error decoding attribute query message");
120         }
121     }
122
123     /**
124      * Builds a response to the attribute query within the request context.
125      * 
126      * @param requestContext current request context
127      * 
128      * @throws ProfileException thrown if there is a problem creating the SAML response
129      */
130     protected void buildResponse(AttributeQueryRequestContext requestContext) throws ProfileException {
131         DateTime issueInstant = new DateTime();
132
133         // create the attribute statement
134         AttributeStatement attributeStatement = buildAttributeStatement(requestContext);
135
136         // create the assertion and add the attribute statement
137         Assertion assertion = buildAssertion(issueInstant, requestContext.getRelyingPartyConfiguration(),
138                 requestContext.getProfileConfiguration());
139         assertion.getAttributeStatements().add(attributeStatement);
140
141         // create the SAML response and add the assertion
142         Response samlResponse = getResponseBuilder().buildObject();
143         populateStatusResponse(samlResponse, issueInstant, requestContext.getAttributeQuery(), requestContext
144                 .getRelyingPartyConfiguration());
145         // TODO handle subject
146         samlResponse.getAssertions().add(assertion);
147
148         // sign the assertion if it should be signed
149         signAssertion(assertion, requestContext.getRelyingPartyConfiguration(), requestContext
150                 .getProfileConfiguration());
151
152         requestContext.setAttributeQueryResponse(samlResponse);
153     }
154
155     /**
156      * Executes a query for attributes and builds a SAML attribute statement from the results.
157      * 
158      * @param requestContext current request context
159      * 
160      * @return attribute statement resulting from the query
161      * 
162      * @throws ProfileException thrown if there is a problem making the query
163      */
164     protected AttributeStatement buildAttributeStatement(AttributeQueryRequestContext requestContext)
165             throws ProfileException {
166         ShibbolethAttributeRequestContext attributeRequestContext = buildAttributeRequestContext(requestContext
167                 .getRelyingPartyId(), requestContext.getUserSession(), requestContext.getProfileRequest());
168
169         try {
170             SAML2AttributeAuthority attributeAuthority = requestContext.getProfileConfiguration()
171                     .getAttributeAuthority();
172             return attributeAuthority.performAttributeQuery(attributeRequestContext);
173         } catch (AttributeRequestException e) {
174             log.error("Error resolving attributes", e);
175             throw new ProfileException("Error resolving attributes", e);
176         }
177     }
178
179     /**
180      * Builds an attribute request context for this request.
181      * 
182      * @param spEntityId entity ID of the service provider
183      * @param userSession current user's session
184      * @param request current request
185      * 
186      * @return the attribute request context
187      * 
188      * @throws ProfileException thrown if the metadata information can not be located for the given service provider
189      */
190     protected ShibbolethAttributeRequestContext buildAttributeRequestContext(String spEntityId, Session userSession,
191             ProfileRequest<ServletRequest> request) throws ProfileException {
192         ServiceInformation spInformation = userSession.getServiceInformation(spEntityId);
193         ShibbolethAttributeRequestContext requestContext = null;
194         try {
195             requestContext = new ShibbolethAttributeRequestContext(getMetadataProvider(),
196                     getRelyingPartyConfiguration(spEntityId));
197             requestContext.setPrincipalName(userSession.getPrincipalID());
198             requestContext.setPrincipalAuthenticationMethod(spInformation.getAuthenticationMethod()
199                     .getAuthenticationMethod());
200             requestContext.setRequest(request.getRawRequest());
201             return requestContext;
202         } catch (MetadataProviderException e) {
203             log.error("Error creating ShibbolethAttributeRequestContext", e);
204             throw new ProfileException("Error retrieving metadata", e);
205         }
206     }
207
208     /**
209      * Writes an aduit log entry indicating the successful response to the attribute request.
210      * 
211      * @param requestContext current request context
212      */
213     protected void writeAuditLogEntry(AttributeQueryRequestContext requestContext) {
214         AuditLogEntry auditLogEntry = new AuditLogEntry();
215         auditLogEntry.setMessageProfile(getProfileId());
216         auditLogEntry.setPrincipalAuthenticationMethod(requestContext.getUserSession().getServiceInformation(
217                 requestContext.getRelyingPartyId()).getAuthenticationMethod().getAuthenticationMethod());
218         auditLogEntry.setPrincipalId(requestContext.getUserSession().getPrincipalID());
219         auditLogEntry.setProviderId(requestContext.getRelyingPartyConfiguration().getProviderId());
220         auditLogEntry.setRelyingPartyId(requestContext.getRelyingPartyId());
221         auditLogEntry.setRequestBinding(requestContext.getMessageDecoder().getBindingURI());
222         auditLogEntry.setRequestId(requestContext.getAttributeQuery().getID());
223         auditLogEntry.setResponseBinding(requestContext.getMessageEncoder().getBindingURI());
224         auditLogEntry.setResponseId(requestContext.getAttributeQueryResponse().getID());
225         getAduitLog().log(Level.CRITICAL, auditLogEntry);
226     }
227
228     /** Basic data structure used to accumulate information as a request is being processed. */
229     protected class AttributeQueryRequestContext {
230
231         /** Current user's session. */
232         private Session userSession;
233
234         /** Current profile request. */
235         private ProfileRequest<ServletRequest> profileRequest;
236
237         /** Decoder used to decode the incoming request. */
238         private MessageDecoder<ServletRequest> messageDecoder;
239
240         /** Current profile response. */
241         private ProfileResponse<ServletResponse> profileResponse;
242
243         /** Encoder used to encode the outgoing response. */
244         private MessageEncoder<ServletResponse> messageEncoder;
245
246         /** Attribute query made by the relying party. */
247         private AttributeQuery attributeQuery;
248
249         /** Attribute query response to the relying party. */
250         private Response attributeQueryResponse;
251
252         /** ID of the relying party. */
253         private String relyingPartyId;
254
255         /** Relying party configuration information. */
256         private RelyingPartyConfiguration relyingPartyConfiguration;
257
258         /** Attribute query profile configuration for the relying party. */
259         private AttributeQueryConfiguration profileConfiguration;
260
261         /**
262          * Constructor.
263          * 
264          * @param request current profile request
265          * @param response current profile response
266          */
267         public AttributeQueryRequestContext(ProfileRequest<ServletRequest> request,
268                 ProfileResponse<ServletResponse> response) {
269             userSession = getSessionManager().getSession(getUserSessionId(request));
270             profileRequest = request;
271             profileResponse = response;
272
273         }
274
275         /**
276          * Gets the attribute query from the relying party.
277          * 
278          * @return attribute query from the relying party
279          */
280         public AttributeQuery getAttributeQuery() {
281             return attributeQuery;
282         }
283
284         /**
285          * Sets the attribute query from the relying party. This also populates the relying party ID, configuration, and
286          * profile configuration using information from the query.
287          * 
288          * @param query attribute query from the relying party
289          */
290         public void setAttributeQuery(AttributeQuery query) {
291             attributeQuery = query;
292             relyingPartyId = attributeQuery.getIssuer().getValue();
293             relyingPartyConfiguration = getRelyingPartyConfigurationManager().getRelyingPartyConfiguration(
294                     relyingPartyId);
295             profileConfiguration = (AttributeQueryConfiguration) relyingPartyConfiguration
296                     .getProfileConfiguration(AttributeQueryConfiguration.PROFILE_ID);
297         }
298
299         /**
300          * Gets the attribute query response.
301          * 
302          * @return attribute query response
303          */
304         public Response getAttributeQueryResponse() {
305             return attributeQueryResponse;
306         }
307
308         /**
309          * Sets the attribute query response.
310          * 
311          * @param response attribute query response
312          */
313         public void setAttributeQueryResponse(Response response) {
314             attributeQueryResponse = response;
315         }
316
317         /**
318          * Gets the decoder used to decode the request.
319          * 
320          * @return decoder used to decode the request
321          */
322         public MessageDecoder<ServletRequest> getMessageDecoder() {
323             return messageDecoder;
324         }
325
326         /**
327          * Sets the decoder used to decode the request.
328          * 
329          * @param decoder decoder used to decode the request
330          */
331         public void setMessageDecoder(MessageDecoder<ServletRequest> decoder) {
332             messageDecoder = decoder;
333         }
334
335         /**
336          * Gets the encoder used to encoder the response.
337          * 
338          * @return encoder used to encoder the response
339          */
340         public MessageEncoder<ServletResponse> getMessageEncoder() {
341             return messageEncoder;
342         }
343
344         /**
345          * Sets the encoder used to encoder the response.
346          * 
347          * @param encoder encoder used to encoder the response
348          */
349         public void setMessageEncoder(MessageEncoder<ServletResponse> encoder) {
350             messageEncoder = encoder;
351         }
352
353         /**
354          * Gets the attribute profile configuration for the relying party.
355          * 
356          * @return attribute profile configuration for the relying party
357          */
358         public AttributeQueryConfiguration getProfileConfiguration() {
359             return profileConfiguration;
360         }
361
362         /**
363          * Gets the current profile request.
364          * 
365          * @return current profile request
366          */
367         public ProfileRequest<ServletRequest> getProfileRequest() {
368             return profileRequest;
369         }
370
371         /**
372          * Gets the current profile response.
373          * 
374          * @return current profile response
375          */
376         public ProfileResponse<ServletResponse> getProfileResponse() {
377             return profileResponse;
378         }
379
380         /**
381          * Gets the configuration information specific to the relying party that made the attribute query.
382          * 
383          * @return configuration information specific to the relying party that made the attribute query
384          */
385         public RelyingPartyConfiguration getRelyingPartyConfiguration() {
386             return relyingPartyConfiguration;
387         }
388
389         /**
390          * Gets the ID of the relying party.
391          * 
392          * @return ID of the relying party
393          */
394         public String getRelyingPartyId() {
395             return relyingPartyId;
396         }
397
398         /**
399          * Gets the current user's session.
400          * 
401          * @return current user's session
402          */
403         public Session getUserSession() {
404             return userSession;
405         }
406     }
407 }