/*
* Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opensaml.saml2.binding.encoding;
import org.opensaml.Configuration;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.common.binding.SAMLMessageContext;
import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.StatusResponseType;
import org.opensaml.saml2.metadata.Endpoint;
import org.opensaml.ws.message.encoder.BaseMessageEncoder;
import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureException;
import org.opensaml.xml.signature.Signer;
import org.opensaml.xml.util.DatatypeHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for SAML 2 message encoders.
*/
public abstract class BaseSAML2MessageEncoder extends BaseMessageEncoder implements SAMLMessageEncoder {
/** Class logger. */
private final Logger log = LoggerFactory.getLogger(BaseSAML2MessageEncoder.class);
/**
* Gets the response URL from the relying party endpoint. If the SAML message is a {@link Response} and the relying
* party endpoint contains a response location then that location is returned otherwise the normal endpoint location
* is returned.
*
* @param messageContext current message context
*
* @return response URL from the relying party endpoint
*
* @throws MessageEncodingException throw if no relying party endpoint is available
*/
protected String getEndpointURL(SAMLMessageContext messageContext) throws MessageEncodingException {
Endpoint endpoint = messageContext.getPeerEntityEndpoint();
if (endpoint == null) {
throw new MessageEncodingException("Endpoint for relying party was null.");
}
if (messageContext.getOutboundMessage() instanceof Response
&& !DatatypeHelper.isEmpty(endpoint.getResponseLocation())) {
return endpoint.getResponseLocation();
} else {
if (DatatypeHelper.isEmpty(endpoint.getLocation())) {
throw new MessageEncodingException("Relying party endpoint location was null or empty.");
}
return endpoint.getLocation();
}
}
/**
* Checks that the relay state is 80 bytes or less if it is not null.
*
* @param relayState relay state to check
*
* @return true if the relay state is not empty and is less than 80 bytes
*/
protected boolean checkRelayState(String relayState) {
if (!DatatypeHelper.isEmpty(relayState)) {
if (relayState.getBytes().length > 80) {
log.warn("Relay state exceeds 80 bytes, some application may not support this.");
}
return true;
}
return false;
}
/**
* Sets the destination attribute on the outbound message if it is a {@link StatusResponseType} message.
*
* @param outboundMessage outbound SAML message
* @param endpointURL destination endpoint
*/
protected void setResponseDestination(SAMLObject outboundMessage, String endpointURL) {
if (outboundMessage instanceof StatusResponseType) {
((StatusResponseType) outboundMessage).setDestination(endpointURL);
}
}
/**
* Signs the given SAML message if it a {@link SignableSAMLObject} and this encoder has signing credentials.
*
* @param messageContext current message context
*
* @throws MessageEncodingException thrown if there is a problem marshalling or signing the outbound message
*/
@SuppressWarnings("unchecked")
protected void signMessage(SAMLMessageContext messageContext) throws MessageEncodingException {
SAMLObject outboundSAML = messageContext.getOutboundSAMLMessage();
Credential signingCredential = messageContext.getOuboundSAMLMessageSigningCredential();
if (outboundSAML instanceof SignableSAMLObject && signingCredential != null) {
SignableSAMLObject signableMessage = (SignableSAMLObject) outboundSAML;
XMLObjectBuilder<Signature> signatureBuilder = Configuration.getBuilderFactory().getBuilder(
Signature.DEFAULT_ELEMENT_NAME);
Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
signature.setSigningCredential(signingCredential);
try {
//TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
//TODO pull binding-specific keyInfoGenName from encoder setting, etc?
SecurityHelper.prepareSignatureParams(signature, signingCredential, null, null);
} catch (SecurityException e) {
throw new MessageEncodingException("Error preparing signature for signing", e);
}
signableMessage.setSignature(signature);
try {
Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(signableMessage);
if (marshaller == null) {
throw new MessageEncodingException("No marshaller registered for "
+ signableMessage.getElementQName() + ", unable to marshall in preperation for signing");
}
marshaller.marshall(signableMessage);
Signer.signObject(signature);
} catch (MarshallingException e) {
log.error("Unable to marshall protocol message in preparation for signing", e);
throw new MessageEncodingException("Unable to marshall protocol message in preparation for signing", e);
} catch (SignatureException e) {
log.error("Unable to sign protocol message", e);
throw new MessageEncodingException("Unable to sign protocol message", e);
}
}
}
}