Revert last change and detect anonymous party earlier in the request processing
[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     /** {@inheritDoc} */
244     public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
245         try{
246         if(getMetadataProvider().getEntityDescriptor(relyingPartyId) == null){
247             log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
248             return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
249         }
250         }catch(MetadataProviderException e){
251             log.error("Unable to look up relying party metadata", e);
252             return null;
253         }
254         
255         return super.getRelyingPartyConfiguration(relyingPartyId);
256     }
257
258     /**
259      * Populates the request context with information.
260      * 
261      * This method requires the the following request context properties to be populated: inbound message transport,
262      * peer entity ID, metadata provider
263      * 
264      * This methods populates the following request context properties: user's session, user's principal name, service
265      * authentication method, peer entity metadata, relying party configuration, local entity ID, outbound message
266      * issuer, local entity metadata
267      * 
268      * @param requestContext current request context
269      * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
270      */
271     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
272         populateRelyingPartyInformation(requestContext);
273         populateAssertingPartyInformation(requestContext);
274         populateSAMLMessageInformation(requestContext);
275         populateProfileInformation(requestContext);
276         populateUserInformation(requestContext);
277     }
278
279     /**
280      * Populates the request context with information about the relying party.
281      * 
282      * This method requires the the following request context properties to be populated: peer entity ID
283      * 
284      * This methods populates the following request context properties: peer entity metadata, relying party
285      * configuration
286      * 
287      * @param requestContext current request context
288      * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
289      */
290     protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
291             throws ProfileException {
292         MetadataProvider metadataProvider = requestContext.getMetadataProvider();
293         String relyingPartyId = requestContext.getPeerEntityId();
294
295         EntityDescriptor relyingPartyMetadata;
296         try {
297             relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
298             requestContext.setPeerEntityMetadata(relyingPartyMetadata);
299         } catch (MetadataProviderException e) {
300             log.error("Error looking up metadata for relying party " + relyingPartyId, e);
301             throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
302         }
303
304         RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
305         if (rpConfig == null) {
306             log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
307             throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
308                     + relyingPartyId);
309         }
310         requestContext.setRelyingPartyConfiguration(rpConfig);
311     }
312
313     /**
314      * Populates the request context with information about the asserting party. Unless overridden,
315      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
316      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)} has already been invoked and the
317      * properties it provides are available in the request context.
318      * 
319      * This method requires the the following request context properties to be populated: metadata provider, relying
320      * party configuration
321      * 
322      * This methods populates the following request context properties: local entity ID, outbound message issuer, local
323      * entity metadata
324      * 
325      * @param requestContext current request context
326      * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
327      */
328     protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
329             throws ProfileException {
330         String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
331         requestContext.setLocalEntityId(assertingPartyId);
332         requestContext.setOutboundMessageIssuer(assertingPartyId);
333
334         try {
335             EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
336                     assertingPartyId);
337             if (localEntityDescriptor != null) {
338                 requestContext.setLocalEntityMetadata(localEntityDescriptor);
339             }
340         } catch (MetadataProviderException e) {
341             log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
342             throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
343         }
344     }
345
346     /**
347      * Populates the request context with information from the inbound SAML message. Unless overridden,
348      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
349      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},and
350      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
351      * properties they provide are available in the request context.
352      * 
353      * 
354      * @param requestContext current request context
355      * 
356      * @throws ProfileException thrown if there is a problem populating the request context with information
357      */
358     protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
359             throws ProfileException;
360
361     /**
362      * Populates the request context with the information about the profile. Unless overridden,
363      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
364      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
365      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)}, and
366      * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
367      * properties they provide are available in the request context.
368      * 
369      * This method requires the the following request context properties to be populated: relying party configuration
370      * 
371      * This methods populates the following request context properties: communication profile ID, profile configuration,
372      * outbound message artifact type, peer entity endpoint
373      * 
374      * @param requestContext current request context
375      * 
376      * @throws ProfileException thrown if there is a problem populating the profile information
377      */
378     protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
379         requestContext.setCommunicationProfileId(getProfileId());
380         AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
381                 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
382         if (profileConfig != null) {
383             requestContext.setProfileConfiguration(profileConfig);
384             requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
385         }
386
387         requestContext.setPeerEntityEndpoint(selectEndpoint(requestContext));
388     }
389
390     /**
391      * Populates the request context with the information about the user if they have an existing session. Unless
392      * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
393      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
394      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
395      * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
396      * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
397      * properties they provide are available in the request context.
398      * 
399      * This method should populate: user's session, user's principal name, and service authentication method
400      * 
401      * @param requestContext current request context
402      * 
403      * @throws ProfileException thrown if there is a problem populating the user's information
404      */
405     protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
406             throws ProfileException;
407
408     /**
409      * Selects the appropriate endpoint for the relying party and stores it in the request context.
410      * 
411      * @param requestContext current request context
412      * 
413      * @return Endpoint selected from the information provided in the request context
414      * 
415      * @throws ProfileException thrown if there is a problem selecting a response endpoint
416      */
417     protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
418
419     /**
420      * Encodes the request's SAML response and writes it to the servlet response.
421      * 
422      * @param requestContext current request context
423      * 
424      * @throws ProfileException thrown if no message encoder is registered for this profiles binding
425      */
426     protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
427         try {
428
429             Endpoint peerEndpoint = requestContext.getPeerEntityEndpoint();
430             if (peerEndpoint == null) {
431                 log
432                         .error("No return endpoint available for relying party {}", requestContext
433                                 .getInboundMessageIssuer());
434                 throw new ProfileException("No peer endpoint available to which to send SAML response");
435             }
436
437             SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
438             if (encoder == null) {
439                 log.error("No outbound message encoder configured for binding {}", requestContext
440                         .getPeerEntityEndpoint().getBinding());
441                 throw new ProfileException("No outbound message encoder configured for binding "
442                         + requestContext.getPeerEntityEndpoint().getBinding());
443             }
444
445             AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
446                     .getProfileConfiguration();
447             if (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
448                     || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
449                             .providesMessageIntegrity(requestContext))) {
450                 Credential signingCredential = null;
451                 if (profileConfig.getSigningCredential() != null) {
452                     signingCredential = profileConfig.getSigningCredential();
453                 } else if (requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential() != null) {
454                     signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
455                 }
456
457                 if (signingCredential == null) {
458                     throw new ProfileException(
459                             "Signing of responses is required but no signing credential is available");
460                 }
461
462                 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
463             }
464
465             log.debug("Encoding response to SAML request {} from relying party {}", requestContext
466                     .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
467
468             requestContext.setMessageEncoder(encoder);
469             encoder.encode(requestContext);
470         } catch (MessageEncodingException e) {
471             throw new ProfileException("Unable to encode response to relying party: "
472                     + requestContext.getInboundMessageIssuer(), e);
473         }
474     }
475
476     /**
477      * Writes an aduit log entry indicating the successful response to the attribute request.
478      * 
479      * @param context current request context
480      */
481     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
482         AuditLogEntry auditLogEntry = new AuditLogEntry();
483         auditLogEntry.setMessageProfile(getProfileId());
484         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
485         auditLogEntry.setPrincipalName(context.getPrincipalName());
486         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
487         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
488         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
489         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
490         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
491         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
492         if (context.getReleasedAttributes() != null) {
493             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
494         }
495
496         getAduitLog().info(auditLogEntry.toString());
497     }
498 }