2 * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package edu.internet2.middleware.shibboleth.idp.profile;
19 import java.util.List;
22 import javax.servlet.http.HttpServletRequest;
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;
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;
51 * Base class for SAML profile handlers.
53 public abstract class AbstractSAMLProfileHandler extends
54 AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
56 /** SAML message audit log. */
57 private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
60 private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
62 /** Generator of IDs which may be used for SAML assertions, requests, etc. */
63 private IdentifierGenerator idGenerator;
65 /** All the SAML message decoders configured for the IdP. */
66 private Map<String, SAMLMessageDecoder> messageDecoders;
68 /** All the SAML message encoders configured for the IdP. */
69 private Map<String, SAMLMessageEncoder> messageEncoders;
71 /** SAML message binding used by inbound messages. */
72 private String inboundBinding;
74 /** SAML message bindings that may be used by outbound messages. */
75 private List<String> supportedOutboundBindings;
77 /** Resolver used to determine active security policy for an incoming request. */
78 private SecurityPolicyResolver securityPolicyResolver;
81 protected AbstractSAMLProfileHandler() {
86 * Gets the resolver used to determine active security policy for an incoming request.
88 * @return resolver used to determine active security policy for an incoming request
90 public SecurityPolicyResolver getSecurityPolicyResolver() {
91 if (securityPolicyResolver == null) {
92 setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
95 return securityPolicyResolver;
99 * Sets the resolver used to determine active security policy for an incoming request.
101 * @param resolver resolver used to determine active security policy for an incoming request
103 public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
104 securityPolicyResolver = resolver;
108 * Gets the audit log for this handler.
110 * @return audit log for this handler
112 protected Logger getAduitLog() {
117 * Gets an ID generator which may be used for SAML assertions, requests, etc.
119 * @return ID generator
121 public IdentifierGenerator getIdGenerator() {
126 * Gets the SAML message binding used by inbound messages.
128 * @return SAML message binding used by inbound messages
130 public String getInboundBinding() {
131 return inboundBinding;
135 * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
137 * @return SAML message decoders configured for the IdP indexed by SAML binding URI
139 public Map<String, SAMLMessageDecoder> getMessageDecoders() {
140 return messageDecoders;
144 * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
146 * @return SAML message encoders configured for the IdP indexed by SAML binding URI
148 public Map<String, SAMLMessageEncoder> getMessageEncoders() {
149 return messageEncoders;
153 * A convenience method for retrieving the SAML metadata provider from the relying party manager.
155 * @return the metadata provider or null
157 public MetadataProvider getMetadataProvider() {
158 SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
159 if (rpcManager != null) {
160 return rpcManager.getMetadataProvider();
167 * Gets the SAML message bindings that may be used by outbound messages.
169 * @return SAML message bindings that may be used by outbound messages
171 public List<String> getSupportedOutboundBindings() {
172 return supportedOutboundBindings;
176 * Gets the user's session, if there is one.
178 * @param inTransport current inbound transport
180 * @return user's session
182 protected Session getUserSession(InTransport inTransport) {
183 HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
184 return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
188 * Gets the user's session based on their principal name.
190 * @param principalName user's principal name
192 * @return the user's session
194 protected Session getUserSession(String principalName) {
195 return getSessionManager().getSession(principalName);
199 * Gets an ID generator which may be used for SAML assertions, requests, etc.
201 * @param generator an ID generator which may be used for SAML assertions, requests, etc
203 public void setIdGenerator(IdentifierGenerator generator) {
204 idGenerator = generator;
208 * Sets the SAML message binding used by inbound messages.
210 * @param binding SAML message binding used by inbound messages
212 public void setInboundBinding(String binding) {
213 inboundBinding = binding;
217 * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
219 * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
221 public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
222 messageDecoders = decoders;
226 * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
228 * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
230 public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
231 messageEncoders = encoders;
235 * Sets the SAML message bindings that may be used by outbound messages.
237 * @param bindings SAML message bindings that may be used by outbound messages
239 public void setSupportedOutboundBindings(List<String> bindings) {
240 supportedOutboundBindings = bindings;
244 public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
246 if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
247 log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
248 return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
250 } catch (MetadataProviderException e) {
251 log.error("Unable to look up relying party metadata", e);
255 return super.getRelyingPartyConfiguration(relyingPartyId);
259 * Populates the request context with information.
261 * This method requires the the following request context properties to be populated: inbound message transport,
262 * peer entity ID, metadata provider
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
268 * @param requestContext current request context
269 * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
271 protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
272 populateRelyingPartyInformation(requestContext);
273 populateAssertingPartyInformation(requestContext);
274 populateSAMLMessageInformation(requestContext);
275 populateProfileInformation(requestContext);
276 populateUserInformation(requestContext);
280 * Populates the request context with information about the relying party.
282 * This method requires the the following request context properties to be populated: peer entity ID
284 * This methods populates the following request context properties: peer entity metadata, relying party
287 * @param requestContext current request context
288 * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
290 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
291 throws ProfileException {
292 MetadataProvider metadataProvider = requestContext.getMetadataProvider();
293 String relyingPartyId = requestContext.getPeerEntityId();
295 EntityDescriptor relyingPartyMetadata;
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);
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 "
310 requestContext.setRelyingPartyConfiguration(rpConfig);
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.
319 * This method requires the the following request context properties to be populated: metadata provider, relying
320 * party configuration
322 * This methods populates the following request context properties: local entity ID, outbound message issuer, local
325 * @param requestContext current request context
326 * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
328 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
329 throws ProfileException {
330 String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
331 requestContext.setLocalEntityId(assertingPartyId);
332 requestContext.setOutboundMessageIssuer(assertingPartyId);
335 EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
337 if (localEntityDescriptor != null) {
338 requestContext.setLocalEntityMetadata(localEntityDescriptor);
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);
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.
354 * @param requestContext current request context
356 * @throws ProfileException thrown if there is a problem populating the request context with information
358 protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
359 throws ProfileException;
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.
369 * This method requires the the following request context properties to be populated: relying party configuration
371 * This methods populates the following request context properties: communication profile ID, profile configuration,
372 * outbound message artifact type, peer entity endpoint
374 * @param requestContext current request context
376 * @throws ProfileException thrown if there is a problem populating the profile information
378 protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
379 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
380 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
381 if (profileConfig != null) {
382 requestContext.setProfileConfiguration(profileConfig);
383 requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
386 Endpoint endpoint = selectEndpoint(requestContext);
387 if (endpoint == null) {
388 log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
389 throw new ProfileException("No peer endpoint available to which to send SAML response");
391 requestContext.setPeerEntityEndpoint(endpoint);
395 * Populates the request context with the information about the user if they have an existing session. Unless
396 * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
397 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
398 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
399 * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
400 * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
401 * properties they provide are available in the request context.
403 * This method should populate: user's session, user's principal name, and service authentication method
405 * @param requestContext current request context
407 * @throws ProfileException thrown if there is a problem populating the user's information
409 protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
410 throws ProfileException;
413 * Selects the appropriate endpoint for the relying party and stores it in the request context.
415 * @param requestContext current request context
417 * @return Endpoint selected from the information provided in the request context
419 * @throws ProfileException thrown if there is a problem selecting a response endpoint
421 protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
424 * Encodes the request's SAML response and writes it to the servlet response.
426 * @param requestContext current request context
428 * @throws ProfileException thrown if no message encoder is registered for this profiles binding
430 protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
432 SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
433 if (encoder == null) {
434 log.error("No outbound message encoder configured for binding {}", requestContext
435 .getPeerEntityEndpoint().getBinding());
436 throw new ProfileException("No outbound message encoder configured for binding "
437 + requestContext.getPeerEntityEndpoint().getBinding());
440 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
441 .getProfileConfiguration();
442 if (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
443 || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
444 .providesMessageIntegrity(requestContext))) {
445 Credential signingCredential = null;
446 if (profileConfig.getSigningCredential() != null) {
447 signingCredential = profileConfig.getSigningCredential();
448 } else if (requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential() != null) {
449 signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
452 if (signingCredential == null) {
453 throw new ProfileException(
454 "Signing of responses is required but no signing credential is available");
457 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
460 log.debug("Encoding response to SAML request {} from relying party {}", requestContext
461 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
463 requestContext.setMessageEncoder(encoder);
464 encoder.encode(requestContext);
465 } catch (MessageEncodingException e) {
466 throw new ProfileException("Unable to encode response to relying party: "
467 + requestContext.getInboundMessageIssuer(), e);
472 * Writes an aduit log entry indicating the successful response to the attribute request.
474 * @param context current request context
476 protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
477 AuditLogEntry auditLogEntry = new AuditLogEntry();
478 auditLogEntry.setMessageProfile(getProfileId());
479 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
480 auditLogEntry.setPrincipalName(context.getPrincipalName());
481 auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
482 auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
483 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
484 auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
485 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
486 auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
487 if (context.getReleasedAttributes() != null) {
488 auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
491 getAduitLog().info(auditLogEntry.toString());