org.opensaml.saml.common.messaging.soap.SAMLSOAPClientContextBuilder Maven / Gradle / Ivy
/*
* Licensed to the University Corporation for Advanced Internet Development,
* Inc. (UCAID) under one or more contributor license agreements. See the
* NOTICE file distributed with this work for additional information regarding
* copyright ownership. The UCAID licenses this file to You 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.saml.common.messaging.soap;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.namespace.QName;
import org.opensaml.core.criterion.EntityIdCriterion;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.messaging.MessageException;
import org.opensaml.messaging.context.BaseContext;
import org.opensaml.messaging.context.InOutOperationContext;
import org.opensaml.messaging.context.MessageContext;
import org.opensaml.messaging.context.navigate.RecursiveTypedParentContextLookup;
import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
import org.opensaml.saml.common.messaging.context.SAMLProtocolContext;
import org.opensaml.saml.common.messaging.context.SAMLSelfEntityContext;
import org.opensaml.saml.criterion.EntityRoleCriterion;
import org.opensaml.saml.criterion.ProtocolCriterion;
import org.opensaml.saml.criterion.RoleDescriptorCriterion;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.RoleDescriptor;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.messaging.HttpClientSecurityContext;
import org.opensaml.soap.client.SOAPClientContext;
import org.opensaml.soap.client.security.SOAPClientSecurityContext;
import com.google.common.base.Function;
import net.shibboleth.utilities.java.support.primitive.StringSupport;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
//TODO when impl finished, document required vs optional data and derivation rules
/**
* Builder {@link InOutOperationContext} instances for SAML SOAP client use cases.
*
* @param the inbound message type
* @param the outbound message type
*/
public class SAMLSOAPClientContextBuilder {
/** The outbound message. **/
private OutboundMessageType outboundMessage;
/** The SAML protocol in use. */
private String protocol;
/** The SAML self entityID. **/
private String selfEntityID;
/** The SAML peer entityID. **/
private String peerEntityID;
/** The SAML peer entity role. **/
private QName peerEntityRole;
/** The SAML peer EntityDescriptor. **/
private EntityDescriptor peerEntityDescriptor;
/** The SAML peer RoleDescriptor. **/
private RoleDescriptor peerRoleDescriptor;
/** TLS CriteriaSet strategy. */
private Function, CriteriaSet> tlsCriteriaSetStrategy;
/** SOAP client message pipeline name. */
private String pipelineName;
/** SOAP client security configuration profile ID. */
private String securityConfigurationProfileId;
/**
* Get the outbound message.
*
* @return the outbound message
*/
@Nullable public OutboundMessageType getOutboundMessage() {
return outboundMessage;
}
/**
* Set the outbound message.
*
* @param message the outbound message
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder setOutboundMessage(
final OutboundMessageType message) {
outboundMessage = message;
return this;
}
/**
* Get the SAML protocol URI.
*
* @return the SAML protocol URI
*/
@Nullable public String getProtocol() {
return protocol;
}
/**
* Set the SAML protocol URI.
*
* @param uri the SAML protocol.
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder setProtocol(
final String uri) {
protocol = uri;
return this;
}
/**
* Get the SAML self entityID.
*
* @return the SAML self entityID
*/
@Nullable public String getSelfEntityID() {
return selfEntityID;
}
/**
* Set the SAML self entityID.
*
* @param entityID the SAML self entityID.
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder setSelfEntityID(
final String entityID) {
selfEntityID = entityID;
return this;
}
/**
* Get the SAML peer entityID.
*
* @return the SAML peer entityID
*/
@Nullable public String getPeerEntityID() {
if (peerEntityID != null) {
return peerEntityID;
} else if (getPeerEntityDescriptor() != null) {
return getPeerEntityDescriptor().getEntityID();
} else {
return null;
}
}
/**
* Set the SAML peer entityID.
*
* @param entityID the SAML peer entityID
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder setPeerEntityID(
final String entityID) {
peerEntityID = entityID;
return this;
}
/**
* Get the SAML peer role.
*
* @return the SAML peer role
*/
@Nullable public QName getPeerEntityRole() {
if (peerEntityRole != null) {
return peerEntityRole;
} else if (getPeerRoleDescriptor() != null) {
if (getPeerRoleDescriptor().getSchemaType() != null) {
return getPeerRoleDescriptor().getSchemaType();
} else {
return getPeerRoleDescriptor().getElementQName();
}
} else {
return null;
}
}
/**
* Set the SAML peer role.
*
* @param role the SAML peer role
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder setPeerEntityRole(
final QName role) {
peerEntityRole = role;
return this;
}
/**
* Get the SAML peer EntityDscriptor.
*
* @return the SAML peer EntityDescriptor
*/
@Nullable public EntityDescriptor getPeerEntityDescriptor() {
if (peerEntityDescriptor != null) {
return peerEntityDescriptor;
} else if (getPeerRoleDescriptor() != null) {
final XMLObject roleParent = getPeerRoleDescriptor().getParent();
if (roleParent instanceof EntityDescriptor) {
return (EntityDescriptor) roleParent;
}
}
return null;
}
/**
* Set the SAML peer EntityDescriptor.
*
* @param entityDescriptor the SAML peer EntityDescriptor
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder setPeerEntityDescriptor(
final EntityDescriptor entityDescriptor) {
peerEntityDescriptor = entityDescriptor;
return this;
}
/**
* Get the SAML peer RoleDescriptor.
*
* @return the SAML peer RoleDescriptor
*/
@Nullable public RoleDescriptor getPeerRoleDescriptor() {
return peerRoleDescriptor;
}
/**
* Set the SAML peer RoleDescriptor.
*
* @param roleDescriptor the SAML peer RoleDescriptor.
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder setPeerRoleDescriptor(
final RoleDescriptor roleDescriptor) {
peerRoleDescriptor = roleDescriptor;
return this;
}
/**
* Get the TLS CriteriaSet strategy.
*
* @return the TLS CriteriaSet strategy, or null
*/
@Nullable public Function, CriteriaSet> getTLSCriteriaSetStrategy() {
if (tlsCriteriaSetStrategy != null) {
return tlsCriteriaSetStrategy;
} else {
return new DefaultTLSCriteriaSetStrategy();
}
}
/**
* Set the TLS CriteriaSet strategy.
*
* @param strategy the strategy
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder
setTLSCriteriaSetStrategy(@Nullable final Function, CriteriaSet> strategy) {
tlsCriteriaSetStrategy = strategy;
return this;
}
/**
* Get the SOAP client message pipeline name to use.
*
* @return the pipeline name, or null
*/
@Nullable public String getPipelineName() {
return pipelineName;
}
/**
* Set the SOAP client message pipeline name to use.
*
* @param name the pipeline name, or null
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder
setPipelineName(@Nullable final String name) {
pipelineName = StringSupport.trimOrNull(name);
return this;
}
/**
* Get the SOAP client security configuration profile ID to use.
*
* @return the client security configuration profile ID, or null
*/
@Nullable public String getSecurityConfigurationProfileId() {
return securityConfigurationProfileId;
}
/**
* Set the SOAP client security configuration profile ID to use.
*
* @param profileId the profile ID, or null
* @return this builder instance
*/
@Nonnull public SAMLSOAPClientContextBuilder
setSecurityConfigurationProfileId(@Nullable final String profileId) {
securityConfigurationProfileId = StringSupport.trimOrNull(profileId);
return this;
}
/**
* Build the new operation context.
*
* @return the operation context
*
* @throws MessageException if any required data is not supplied and can not be derived from other supplied data
*/
public InOutOperationContext build() throws MessageException {
if (getOutboundMessage() == null) {
errorMissingData("Outbound message");
}
final MessageContext outboundContext = new MessageContext();
outboundContext.setMessage(getOutboundMessage());
final Function, CriteriaSet> tlsStrategy = getTLSCriteriaSetStrategy();
if (tlsStrategy != null) {
outboundContext.getSubcontext(HttpClientSecurityContext.class, true)
.setTLSCriteriaSetStrategy(tlsStrategy);
}
final InOutOperationContext opContext =
new InOutOperationContext<>(null, outboundContext);
// This is just so it's easy to change.
final BaseContext parent = opContext;
if (getProtocol() != null) {
parent.getSubcontext(SAMLProtocolContext.class, true).setProtocol(getProtocol());
}
if (getPipelineName() != null) {
parent.getSubcontext(SOAPClientContext.class, true).setPipelineName(getPipelineName());
}
if (getSecurityConfigurationProfileId() != null) {
parent.getSubcontext(SOAPClientSecurityContext.class, true).setSecurityConfigurationProfileId(
getSecurityConfigurationProfileId());
}
//TODO is this required always?
final String selfID = getSelfEntityID();
if (selfID != null) {
final SAMLSelfEntityContext selfContext = parent.getSubcontext(SAMLSelfEntityContext.class, true);
selfContext.setEntityId(selfID);
}
// Both of these required, either supplied or derived
final String peerID = getPeerEntityID();
if (peerID == null) {
errorMissingData("Peer entityID");
}
final QName peerRoleName = getPeerEntityRole();
if (peerRoleName == null) {
errorMissingData("Peer role");
}
final SAMLPeerEntityContext peerContext = parent.getSubcontext(SAMLPeerEntityContext.class, true);
peerContext.setEntityId(peerID);
peerContext.setRole(peerRoleName);
// Both optional, could be resolved in SOAP handling pipeline by handler(s)
final SAMLMetadataContext metadataContext = peerContext.getSubcontext(SAMLMetadataContext.class, true);
metadataContext.setEntityDescriptor(getPeerEntityDescriptor());
metadataContext.setRoleDescriptor(getPeerRoleDescriptor());
return opContext;
}
/**
* Convenience method to report out an error due to missing required data.
*
* @param details the error details
* @throws MessageException the error to be reported out
*/
private void errorMissingData(@Nonnull final String details) throws MessageException {
throw new MessageException("Required context data was not supplied or derivable: " + details);
}
/** Default TLS CriteriaSet strategy function. */
public static class DefaultTLSCriteriaSetStrategy implements Function, CriteriaSet> {
/** {@inheritDoc} */
public CriteriaSet apply(@Nullable final MessageContext messageContext) {
final CriteriaSet criteria = new CriteriaSet();
criteria.add(new UsageCriterion(UsageType.SIGNING));
if (messageContext == null) {
return criteria;
}
// This should be consistent with what build() does above.
final BaseContext parent = new RecursiveTypedParentContextLookup<>(InOutOperationContext.class)
.apply(messageContext);
if (parent == null) {
return criteria;
}
final SAMLProtocolContext protocolContext = parent.getSubcontext(SAMLProtocolContext.class);
if (protocolContext != null && protocolContext.getProtocol() != null) {
criteria.add(new ProtocolCriterion(protocolContext.getProtocol()));
}
final SAMLPeerEntityContext peerContext = parent.getSubcontext(SAMLPeerEntityContext.class);
if (peerContext != null) {
if (peerContext.getEntityId() != null) {
criteria.add(new EntityIdCriterion(peerContext.getEntityId()));
}
if (peerContext.getRole() != null) {
criteria.add(new EntityRoleCriterion(peerContext.getRole()));
}
final SAMLMetadataContext metadataContext = peerContext.getSubcontext(SAMLMetadataContext.class);
if (metadataContext != null && metadataContext.getRoleDescriptor() != null) {
criteria.add(new RoleDescriptorCriterion(metadataContext.getRoleDescriptor()));
}
}
return criteria;
}
}
}