package org.atricore.idbus.capabilities.sso.main.binding; import oasis.names.tc.saml._2_0.metadata.*; import oasis.names.tc.saml._2_0.protocol.ArtifactResolveType; import oasis.names.tc.saml._2_0.protocol.ArtifactResponseType; import oasis.names.tc.saml._2_0.protocol.RequestAbstractType; import oasis.names.tc.saml._2_0.protocol.StatusResponseType; import org.apache.camel.Exchange; import org.apache.camel.Message; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.atricore.idbus.capabilities.sso.main.SSOException; import org.atricore.idbus.capabilities.sso.main.binding.plans.SamlR2ArtifactToSamlR2ArtifactResolvePlan; import org.atricore.idbus.capabilities.sso.support.SAMLR2Constants; import org.atricore.idbus.capabilities.sso.support.binding.SSOBinding; import org.atricore.idbus.kernel.main.federation.metadata.*; import org.atricore.idbus.kernel.main.mediation.*; import org.atricore.idbus.kernel.main.mediation.binding.BindingChannel; import org.atricore.idbus.kernel.main.mediation.camel.AbstractCamelMediator; import org.atricore.idbus.kernel.main.mediation.camel.component.binding.AbstractMediationHttpBinding; import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationMessage; import org.atricore.idbus.kernel.main.mediation.channel.FederationChannel; import org.atricore.idbus.kernel.main.mediation.endpoint.IdentityMediationEndpoint; import org.atricore.idbus.kernel.main.mediation.provider.FederatedLocalProvider; import org.atricore.idbus.kernel.planning.*; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; import static org.atricore.idbus.capabilities.sso.main.common.plans.SSOPlanningConstants.*; /** * @author <a href="mailto:sgonzalez@atricore.org">Sebastian Gonzalez Oyuela</a> * @version $Id$ */ public class SamlR2HttpArtifactBinding extends AbstractMediationHttpBinding { private static final Log logger = LogFactory.getLog(SamlR2HttpArtifactBinding.class); private String artifactParameterName = "SAMLart"; private SamlArtifactEncoder artifactEncoder; public SamlR2HttpArtifactBinding(Channel channel) { super(SSOBinding.SAMLR2_ARTIFACT.getValue(), channel); artifactEncoder = new SamlR2ArtifactEncoderImpl(); } public MediationMessage createMessage(CamelMediationMessage message) { // The nested exchange contains HTTP information Exchange exchange = message.getExchange().getExchange(); logger.debug("Create Message Body from exchange " + exchange.getClass().getName()); Message httpMsg = exchange.getIn(); if (httpMsg.getHeader("http.requestMethod") == null) { throw new IllegalArgumentException("Unknown message, no valid HTTP Method header found!"); } try { // HTTP Request Parameters from HTTP Request body MediationState state = createMediationState(exchange); // HTTP-Artifact SSOBinding supports the following parameters String samlArtStr = state.getTransientVariable(artifactParameterName); String relayState = state.getTransientVariable("RelayState"); if (samlArtStr == null || "".equals(samlArtStr)) throw new IllegalArgumentException("'"+artifactParameterName+"' parameter not found!"); // Access issuer Reolver endpoint to get value! SamlArtifact samlArtifact = getEncoder().decode(samlArtStr); String sourceId = samlArtifact.getSourceID(); // Here, we assume that our artifacts always have sourceId CircleOfTrustMemberDescriptor resolverMemberDescr = ((FederatedLocalProvider)this.getProvider()).getCotManager().lookupMemberById(sourceId); if (resolverMemberDescr == null) { /* Unknown SOURCE ID! */ logger.warn("Unknown SAML Artifact SourceID ["+sourceId+"]"); throw new SSOException("Unknown SAML Artifact SourceID ["+sourceId+"]"); } // Since this is the destination channel of a SAML Message, it MUST be a FederationChannel FederationChannel fChannel = (FederationChannel) channel; CircleOfTrustMemberDescriptor memberDescr = fChannel.getMember(); // Find SAML 2.0 Metadata MetadataEntry md = resolverMemberDescr.getMetadata(); EntityDescriptorType samlMd = (EntityDescriptorType) md.getEntry(); // Find ArtifactResolutionService endpoint EndpointDescriptor samlArtResolveEd = resolveArtifactResolveEndpoint(samlMd, samlArtifact.getEndpointIndex()); if (logger.isTraceEnabled()) logger.trace("Resolving artifact at " + samlArtResolveEd); ArtifactResolveType req = buildArtifactResolve( memberDescr, resolverMemberDescr, samlArtStr, samlArtResolveEd, fChannel); // Resolve Artifact using binding to send SOAP message ArtifactResponseType res = (ArtifactResponseType) this.channel.getIdentityMediator().sendMessage(req, samlArtResolveEd, channel); java.lang.Object msgValue = res.getAny(); if (logger.isTraceEnabled()) logger.trace("Received SAML Message : " + msgValue); // See if we're dealing with a request or a response if (msgValue instanceof JAXBElement) { msgValue = ((JAXBElement)msgValue).getValue(); } if (msgValue instanceof RequestAbstractType) { RequestAbstractType samlRequest = (RequestAbstractType) msgValue; logger.debug("Received SAML Request " + samlRequest.getID()); // Store relay state to send it back later if (relayState != null) { state.setLocalVariable("urn:org:atricore:idbus:samr2:protocol:relayState:" + samlRequest.getID(), relayState); } return new MediationMessageImpl<RequestAbstractType>(httpMsg.getMessageId(), samlRequest, null, null, relayState, null, state); } else if (msgValue instanceof StatusResponseType) { StatusResponseType samlResponse = (StatusResponseType) msgValue; logger.debug("Received SAML Response " + samlResponse.getID()); return new MediationMessageImpl<StatusResponseType>(httpMsg.getMessageId(), samlResponse, null, null, relayState, null, state); } else { throw new RuntimeException("Unknown SAML 2.0 Type : " + msgValue); } } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } public void copyMessageToExchange(CamelMediationMessage samlOut, Exchange exchange) { try { MediationMessage out = samlOut.getMessage(); EndpointDescriptor ed = out.getDestination(); // ------------------------------------------------------------ // Validate received message // ------------------------------------------------------------ assert ed != null : "Mediation Response MUST Provide a destination"; if (out.getContent() == null) { throw new NullPointerException("Cannot send HTTP Artifact response for null content. Endpoint location " + ed.getLocation()); } // ------------------------------------------------------------ // Create HTML Redirect // ------------------------------------------------------------ if (logger.isDebugEnabled()) logger.debug("Creating HTML Artifact to " + ed.getLocation()); String msgName = null; String destAlias = null; java.lang.Object msgValue = out.getContent(); String element = out.getContentType(); boolean isResponse = false; String relayState = out.getRelayState(); if (out.getContent() instanceof RequestAbstractType) { msgName = "SAMLRequest"; } else if (out.getContent() instanceof StatusResponseType) { msgName = "SAMLResponse"; isResponse = true; StatusResponseType samlResponse = (StatusResponseType) out.getContent(); if (samlResponse.getInResponseTo() != null) { String rs = (String) out.getState().getLocalVariable("urn:org:atricore:idbus:samr2:protocol:relayState:" + samlResponse.getInResponseTo()); if (relayState != null && rs != null && !relayState.equals(rs)) { relayState = rs; logger.warn("Provided relay state does not match stored state : " + relayState + " : " + rs + ", forchandleCrossOriginResourceSharing(exchange);ing " + relayState); } } } else { logger.error("Unsupported SAML 2.0 Type for " + out.getContent()); throw new RuntimeException("Unsupported SAML 2.0 Type for " + out.getContent()); } if (msgValue == null) { throw new NullPointerException("Cannot send null content to " + ed.getLocation()); } MessageQueueManager aqm = getArtifactQueueManager(); // Wrapp SAML Message, including type SamlMessageWrapper wrapper = new SamlMessageWrapper(element, msgValue); Artifact artifact = aqm.pushMessage(wrapper); CircleOfTrustMemberDescriptor cotMember = getCotMember(destAlias); SamlArtifact samlArtifact = new SamlArtifact( SAMLR2Constants.SAML_ARTIFACT_TYPE, 0, cotMember.getId(), artifact.getContent()); if (logger.isTraceEnabled()) logger.trace("Created SAML Artifact " + samlArtifact + " for AQM artifact " + artifact.getContent()); String samlArtifactEnc = getEncoder().encode(samlArtifact); String qryString = "?" + artifactParameterName + "=" + samlArtifactEnc; if (out.getRelayState() != null) { qryString += "&RelayState=" + relayState; } Message httpOut = exchange.getOut(); Message httpIn = exchange.getIn(); String redirLocation = this.buildHttpTargetLocation(httpIn, ed, isResponse) + qryString; // ------------------------------------------------------------ // Prepare HTTP Resposne // ------------------------------------------------------------ copyBackState(out.getState(), exchange); httpOut.getHeaders().put("Cache-Control", "no-cache, no-store"); httpOut.getHeaders().put("Pragma", "no-cache"); httpOut.getHeaders().put("http.responseCode", 302); httpOut.getHeaders().put("Content-Type", "text/html"); httpOut.getHeaders().put("Location", redirLocation); handleCrossOriginResourceSharing(exchange); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } protected MessageQueueManager getArtifactQueueManager() { AbstractCamelMediator mediator = (AbstractCamelMediator) getChannel().getIdentityMediator(); return mediator.getArtifactQueueManager(); } public String getArtifactParameterName() { return artifactParameterName; } public void setArtifactParameterName(String artifactParameterName) { this.artifactParameterName = artifactParameterName; } public SamlArtifactEncoder getEncoder() { return getArtifactEncoder(); } public SamlArtifactEncoder getArtifactEncoder() { return artifactEncoder; } public void setArtifactEncoder(SamlArtifactEncoder artifactEncoder) { this.artifactEncoder = artifactEncoder; } protected EndpointDescriptor resolveArtifactResolveEndpoint(EntityDescriptorType samlMd, int edIdx) throws CircleOfTrustManagerException { // We need to find out if the entity is external or not ! boolean preferLocalBindings = ((FederatedLocalProvider)this.getProvider()).getCotManager().isLocalMember(samlMd.getEntityID()); EndpointType samlEndpoint = null; for (RoleDescriptorType roleDescriptor : samlMd.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) { if (roleDescriptor instanceof SSODescriptorType) { SSODescriptorType ssoDescriptor = (SSODescriptorType) roleDescriptor; EndpointType localSamlEndpoint = null; EndpointType soapSamlEndpoint = null; EndpointType defaultSamlEndpoint = null; for (IndexedEndpointType samlIdxEndpoint : ssoDescriptor.getArtifactResolutionService()) { if (samlIdxEndpoint.getIsDefault() != null && samlIdxEndpoint.getIsDefault() && (samlIdxEndpoint.getBinding().equals(SSOBinding.SAMLR2_LOCAL.getValue()) || samlIdxEndpoint.getBinding().equals(SSOBinding.SAMLR2_SOAP.getValue()))) { defaultSamlEndpoint = samlIdxEndpoint; } if (edIdx > 0) { if (edIdx == samlIdxEndpoint.getIndex()) { samlEndpoint = samlIdxEndpoint; break; } } else { if (samlIdxEndpoint.getBinding().equals(SSOBinding.SAMLR2_LOCAL.getValue())) { localSamlEndpoint = samlIdxEndpoint; } if (samlIdxEndpoint.getBinding().equals(SSOBinding.SAMLR2_SOAP.getValue())) { soapSamlEndpoint = samlIdxEndpoint; } } } if (preferLocalBindings && localSamlEndpoint != null) samlEndpoint = localSamlEndpoint; if (samlEndpoint == null) samlEndpoint = soapSamlEndpoint; if (samlEndpoint == null) samlEndpoint = defaultSamlEndpoint; if (samlEndpoint != null) break; } if (samlEndpoint != null) break; } if (samlEndpoint == null) throw new RuntimeException("Cannot resovle SAML 2.0 ArtifactResolutionService for entity " + samlMd.getEntityID()); return new EndpointDescriptorImpl("ArtifactResolutionService", "ArtifactResolveType", samlEndpoint.getBinding(), samlEndpoint.getLocation(), samlEndpoint.getResponseLocation()); } protected ArtifactResolveType buildArtifactResolve(CircleOfTrustMemberDescriptor member, CircleOfTrustMemberDescriptor destMember, String samlArtEnc, EndpointDescriptor ed, FederationChannel channel ) throws IdentityPlanningException, SSOException { IdentityPlan identityPlan = findIdentityPlanOfType(SamlR2ArtifactToSamlR2ArtifactResolvePlan.class); IdentityPlanExecutionExchange idPlanExchange = new IdentityPlanExecutionExchangeImpl(); // TODO : Resolve endpoint IdentityMediationEndpoint idEndpoint = null; // Publish some important attributes: // Circle of trust will allow actions to access identity configuration idPlanExchange.setProperty(VAR_COT, ((FederatedLocalProvider)this.getProvider()).getCotManager().getCot()); idPlanExchange.setProperty(VAR_COT_MEMBER, member); idPlanExchange.setProperty(VAR_CHANNEL, this.channel); idPlanExchange.setProperty(VAR_ENDPOINT, idEndpoint); idPlanExchange.setProperty(VAR_DESTINATION_COT_MEMBER, destMember); idPlanExchange.setProperty(VAR_DESTINATION_ENDPOINT_DESCRIPTOR, ed); idPlanExchange.setProperty(VAR_COT_MEMBER, channel.getMember()); idPlanExchange.setProperty(VAR_RESPONSE_CHANNEL, channel); // Create in/out artifacts // saml artifact IdentityArtifact in = new IdentityArtifactImpl(new QName(SAMLR2Constants.SAML_PROTOCOL_NS, "SAMLart"), samlArtEnc ); idPlanExchange.setIn(in); // ArtifactResolve IdentityArtifact<ArtifactResolveType> out = new IdentityArtifactImpl<ArtifactResolveType>(new QName(SAMLR2Constants.SAML_PROTOCOL_NS, "ArtifactResolve"), new ArtifactResolveType()); idPlanExchange.setOut(out); // Prepare execution identityPlan.prepare(idPlanExchange); // Perform execution identityPlan.perform(idPlanExchange); if (!idPlanExchange.getStatus().equals(IdentityPlanExecutionStatus.SUCCESS)) { throw new SSOException("Identity plan returned : " + idPlanExchange.getStatus()); } if (idPlanExchange.getOut() == null) throw new SSOException("Plan Exchange OUT must not be null!"); return (ArtifactResolveType) idPlanExchange.getOut().getContent(); } protected IdentityPlan findIdentityPlanOfType(Class planClass) throws SSOException { for (IdentityMediationEndpoint e : channel.getEndpoints()) { if (e.getIdentityPlans() == null) continue; for (IdentityPlan p : e.getIdentityPlans()) { if (planClass.isInstance(p)) return p; } } logger.warn("No identity plan of class " + planClass.getName() + " was found for binding " + SamlR2HttpArtifactBinding.class.getSimpleName()); return null; } protected CircleOfTrustMemberDescriptor getCotMember(String destAlias) throws SSOException { FederationChannel fc = getFederationChannel(destAlias); return fc.getMember(); } /** * Gest the FederationChannel that is used to send a message using HTTP-Artifact binding. */ protected FederationChannel getFederationChannel(String destAlias) throws SSOException { // We're bound to a FederationChannel, return it if (channel instanceof FederationChannel) { // The binding is working with a FC return (FederationChannel) channel; } // We're bound to a different type of channel, // try to get the federation channel based on the destination entity // Federated Provdier FederatedLocalProvider provider = getFederatedProvider(); // Default FederationChannel FederationChannel fChannel = provider.getChannel(); // Specific FederationChannels for (FederationChannel f : provider.getChannels()) { if (f.getMember().getAlias().equals(destAlias)) fChannel = f; } return fChannel; } /** * Returns the FederatedLocalProvider used to send a SAML Message using HTTP-Artifact Binding * @return * @throws org.atricore.idbus.capabilities.sso.main.SSOException */ protected FederatedLocalProvider getFederatedProvider() throws SSOException { if (channel instanceof FederationChannel) { // The binding is working with a FC FederationChannel fChannel = (FederationChannel) channel; return fChannel.getFederatedProvider(); } if (channel instanceof BindingChannel) { BindingChannel bChannel = (BindingChannel) channel; FederatedLocalProvider provider = bChannel.getFederatedProvider(); return provider; } throw new SSOException("Unsupported channel type : " + channel); } }