Lots of code cleanup
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / AbstractSAMLProfileHandler.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;
18
19 import java.util.List;
20 import java.util.Map;
21
22 import javax.servlet.http.HttpServletRequest;
23
24 import org.opensaml.common.IdentifierGenerator;
25 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
26 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
27 import org.opensaml.saml2.metadata.Endpoint;
28 import org.opensaml.saml2.metadata.EntityDescriptor;
29 import org.opensaml.saml2.metadata.provider.MetadataProvider;
30 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
31 import org.opensaml.ws.message.encoder.MessageEncodingException;
32 import org.opensaml.ws.security.SecurityPolicyResolver;
33 import org.opensaml.ws.transport.InTransport;
34 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
35 import org.opensaml.xml.security.credential.Credential;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
40 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
41 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
42 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
43 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
44 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
45 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
46 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
47 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
48 import edu.internet2.middleware.shibboleth.idp.session.Session;
49
50 /**
51  * Base class for SAML profile handlers.
52  */
53 public abstract class AbstractSAMLProfileHandler extends
54         AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
55
56     /** SAML message audit log. */
57     private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
58
59     /** Class logger. */
60     private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
61
62     /** Generator of IDs which may be used for SAML assertions, requests, etc. */
63     private IdentifierGenerator idGenerator;
64
65     /** All the SAML message decoders configured for the IdP. */
66     private Map<String, SAMLMessageDecoder> messageDecoders;
67
68     /** All the SAML message encoders configured for the IdP. */
69     private Map<String, SAMLMessageEncoder> messageEncoders;
70
71     /** SAML message binding used by inbound messages. */
72     private String inboundBinding;
73
74     /** SAML message bindings that may be used by outbound messages. */
75     private List<String> supportedOutboundBindings;
76
77     /** Resolver used to determine active security policy for an incoming request. */
78     private SecurityPolicyResolver securityPolicyResolver;
79
80     /** Constructor. */
81     protected AbstractSAMLProfileHandler() {
82         super();
83     }
84
85     /**
86      * Gets the resolver used to determine active security policy for an incoming request.
87      * 
88      * @return resolver used to determine active security policy for an incoming request
89      */
90     public SecurityPolicyResolver getSecurityPolicyResolver() {
91         if (securityPolicyResolver == null) {
92             setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
93         }
94
95         return securityPolicyResolver;
96     }
97
98     /**
99      * Sets the resolver used to determine active security policy for an incoming request.
100      * 
101      * @param resolver resolver used to determine active security policy for an incoming request
102      */
103     public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
104         securityPolicyResolver = resolver;
105     }
106
107     /**
108      * Gets the audit log for this handler.
109      * 
110      * @return audit log for this handler
111      */
112     protected Logger getAduitLog() {
113         return auditLog;
114     }
115
116     /**
117      * Gets an ID generator which may be used for SAML assertions, requests, etc.
118      * 
119      * @return ID generator
120      */
121     public IdentifierGenerator getIdGenerator() {
122         return idGenerator;
123     }
124
125     /**
126      * Gets the SAML message binding used by inbound messages.
127      * 
128      * @return SAML message binding used by inbound messages
129      */
130     public String getInboundBinding() {
131         return inboundBinding;
132     }
133
134     /**
135      * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
136      * 
137      * @return SAML message decoders configured for the IdP indexed by SAML binding URI
138      */
139     public Map<String, SAMLMessageDecoder> getMessageDecoders() {
140         return messageDecoders;
141     }
142
143     /**
144      * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
145      * 
146      * @return SAML message encoders configured for the IdP indexed by SAML binding URI
147      */
148     public Map<String, SAMLMessageEncoder> getMessageEncoders() {
149         return messageEncoders;
150     }
151
152     /**
153      * A convenience method for retrieving the SAML metadata provider from the relying party manager.
154      * 
155      * @return the metadata provider or null
156      */
157     public MetadataProvider getMetadataProvider() {
158         SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
159         if (rpcManager != null) {
160             return rpcManager.getMetadataProvider();
161         }
162
163         return null;
164     }
165
166     /**
167      * Gets the SAML message bindings that may be used by outbound messages.
168      * 
169      * @return SAML message bindings that may be used by outbound messages
170      */
171     public List<String> getSupportedOutboundBindings() {
172         return supportedOutboundBindings;
173     }
174
175     /**
176      * Gets the user's session, if there is one.
177      * 
178      * @param inTransport current inbound transport
179      * 
180      * @return user's session
181      */
182     protected Session getUserSession(InTransport inTransport) {
183         HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
184         return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
185     }
186
187     /**
188      * Gets the user's session based on their principal name.
189      * 
190      * @param principalName user's principal name
191      * 
192      * @return the user's session
193      */
194     protected Session getUserSession(String principalName) {
195         return getSessionManager().getSession(principalName);
196     }
197
198     /**
199      * Gets an ID generator which may be used for SAML assertions, requests, etc.
200      * 
201      * @param generator an ID generator which may be used for SAML assertions, requests, etc
202      */
203     public void setIdGenerator(IdentifierGenerator generator) {
204         idGenerator = generator;
205     }
206
207     /**
208      * Sets the SAML message binding used by inbound messages.
209      * 
210      * @param binding SAML message binding used by inbound messages
211      */
212     public void setInboundBinding(String binding) {
213         inboundBinding = binding;
214     }
215
216     /**
217      * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
218      * 
219      * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
220      */
221     public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
222         messageDecoders = decoders;
223     }
224
225     /**
226      * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
227      * 
228      * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
229      */
230     public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
231         messageEncoders = encoders;
232     }
233
234     /**
235      * Sets the SAML message bindings that may be used by outbound messages.
236      * 
237      * @param bindings SAML message bindings that may be used by outbound messages
238      */
239     public void setSupportedOutboundBindings(List<String> bindings) {
240         supportedOutboundBindings = bindings;
241     }
242
243     /**
244      * Populates the request context with information.
245      * 
246      * This method requires the the following request context properties to be populated: inbound message transport,
247      * peer entity ID, metadata provider
248      * 
249      * This methods populates the following request context properties: user's session, user's principal name, service
250      * authentication method, peer entity metadata, relying party configuration, local entity ID, outbound message
251      * issuer, local entity metadata
252      * 
253      * @param requestContext current request context
254      * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
255      */
256     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
257         populateRelyingPartyInformation(requestContext);
258         populateAssertingPartyInformation(requestContext);
259         populateProfileInformation(requestContext);
260         populateSAMLMessageInformation(requestContext);
261         populateUserInformation(requestContext);
262     }
263
264     /**
265      * Populates the request context with information about the relying party.
266      * 
267      * This method requires the the following request context properties to be populated: peer entity ID
268      * 
269      * This methods populates the following request context properties: peer entity metadata, relying party
270      * configuration
271      * 
272      * @param requestContext current request context
273      * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
274      */
275     protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
276             throws ProfileException {
277         MetadataProvider metadataProvider = requestContext.getMetadataProvider();
278         String relyingPartyId = requestContext.getPeerEntityId();
279
280         EntityDescriptor relyingPartyMetadata;
281         try {
282             relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
283         } catch (MetadataProviderException e) {
284             log.error("Error looking up metadata for relying party " + relyingPartyId, e);
285             throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
286         }
287         
288         RelyingPartyConfiguration rpConfig = null;
289         if (relyingPartyMetadata != null) {
290             requestContext.setPeerEntityMetadata(relyingPartyMetadata);
291             rpConfig = getRelyingPartyConfiguration(relyingPartyId);
292         } else {
293             log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
294             rpConfig = getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
295         }
296
297         if (rpConfig == null) {
298             log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
299             throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
300                     + relyingPartyId);
301         }
302         requestContext.setRelyingPartyConfiguration(rpConfig);
303     }
304
305     /**
306      * Populates the request context with information about the asserting party. Unless overridden,
307      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
308      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)} has already been invoked and the
309      * properties it provides are available in the request context.
310      * 
311      * This method requires the the following request context properties to be populated: metadata provider, relying
312      * party configuration
313      * 
314      * This methods populates the following request context properties: local entity ID, outbound message issuer, local
315      * entity metadata
316      * 
317      * @param requestContext current request context
318      * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
319      */
320     protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
321             throws ProfileException {
322         String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
323         requestContext.setLocalEntityId(assertingPartyId);
324         requestContext.setOutboundMessageIssuer(assertingPartyId);
325
326         try {
327             EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
328                     assertingPartyId);
329             if (localEntityDescriptor != null) {
330                 requestContext.setLocalEntityMetadata(localEntityDescriptor);
331             }
332         } catch (MetadataProviderException e) {
333             log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
334             throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
335         }
336     }
337
338     /**
339      * Populates the request context with the information about the profile. Unless overridden,
340      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
341      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},and
342      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
343      * properties they provide are available in the request context.
344      * 
345      * This method requires the the following request context properties to be populated: relying party configuration
346      * 
347      * This methods populates the following request context properties: communication profile ID, profile configuration,
348      * outbound message artifact type, peer entity endpoint
349      * 
350      * @param requestContext current request context
351      * 
352      * @throws ProfileException thrown if there is a problem populating the profile information
353      */
354     protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
355         requestContext.setCommunicationProfileId(getProfileId());
356         AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
357                 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
358         requestContext.setProfileConfiguration(profileConfig);
359         requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
360         requestContext.setPeerEntityEndpoint(selectEndpoint(requestContext));
361     }
362
363     /**
364      * Populates the request context with information from the inbound SAML message. Unless overridden,
365      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
366      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
367      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)}, and
368      * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)} have already been invoked and the properties
369      * they provide are available in the request context.
370      * 
371      * @param requestContext current request context
372      * 
373      * @throws ProfileException thrown if there is a problem populating the request context with information
374      */
375     protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
376             throws ProfileException;
377
378     /**
379      * Populates the request context with the information about the user if they have an existing session. Unless
380      * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
381      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
382      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
383      * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
384      * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
385      * properties they provide are available in the request context.
386      * 
387      * This method should populate: user's session, user's principal name, and service authentication method
388      * 
389      * @param requestContext current request context
390      * 
391      * @throws ProfileException thrown if there is a problem populating the user's information
392      */
393     protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
394             throws ProfileException;
395
396     /**
397      * Selects the appropriate endpoint for the relying party and stores it in the request context.
398      * 
399      * @param requestContext current request context
400      * 
401      * @return Endpoint selected from the information provided in the request context
402      * 
403      * @throws ProfileException thrown if there is a problem selecting a response endpoint
404      */
405     protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
406
407     /**
408      * Encodes the request's SAML response and writes it to the servlet response.
409      * 
410      * @param requestContext current request context
411      * 
412      * @throws ProfileException thrown if no message encoder is registered for this profiles binding
413      */
414     protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
415         try {
416
417             Endpoint peerEndpoint = requestContext.getPeerEntityEndpoint();
418             if (peerEndpoint == null) {
419                 log
420                         .error("No return endpoint available for relying party {}", requestContext
421                                 .getInboundMessageIssuer());
422                 throw new ProfileException("No peer endpoint available to which to send SAML response");
423             }
424
425             SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
426             if (encoder == null) {
427                 log.error("No outbound message encoder configured for binding {}", requestContext
428                         .getPeerEntityEndpoint().getBinding());
429                 throw new ProfileException("No outbound message encoder configured for binding "
430                         + requestContext.getPeerEntityEndpoint().getBinding());
431             }
432
433             AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
434                     .getProfileConfiguration();
435             if (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
436                     || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
437                             .providesMessageIntegrity(requestContext))) {
438                 Credential signingCredential = null;
439                 if (profileConfig.getSigningCredential() != null) {
440                     signingCredential = profileConfig.getSigningCredential();
441                 } else if (requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential() != null) {
442                     signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
443                 }
444
445                 if (signingCredential == null) {
446                     throw new ProfileException(
447                             "Signing of responses is required but no signing credential is available");
448                 }
449
450                 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
451             }
452
453             log.debug("Encoding response to SAML request {} from relying party {}", requestContext
454                     .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
455
456             requestContext.setMessageEncoder(encoder);
457             encoder.encode(requestContext);
458         } catch (MessageEncodingException e) {
459             throw new ProfileException("Unable to encode response to relying party: "
460                     + requestContext.getInboundMessageIssuer(), e);
461         }
462     }
463
464     /**
465      * Writes an aduit log entry indicating the successful response to the attribute request.
466      * 
467      * @param context current request context
468      */
469     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
470         AuditLogEntry auditLogEntry = new AuditLogEntry();
471         auditLogEntry.setMessageProfile(getProfileId());
472         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
473         auditLogEntry.setPrincipalName(context.getPrincipalName());
474         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
475         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
476         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
477         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
478         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
479         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
480         if (context.getReleasedAttributes() != null) {
481             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
482         }
483
484         getAduitLog().info(auditLogEntry.toString());
485     }
486 }