View Javadoc

    1   /*
    2    * Licensed to the University Corporation for Advanced Internet Development,
    3    * Inc. (UCAID) under one or more contributor license agreements.  See the
    4    * NOTICE file distributed with this work for additional information regarding
    5    * copyright ownership. The UCAID licenses this file to You under the Apache
    6    * License, Version 2.0 (the "License"); you may not use this file except in
    7    * compliance with the License.  You may obtain a copy of the License at
    8    *
    9    *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package edu.internet2.middleware.shibboleth.idp.profile.saml1;
    19
    20  import java.util.ArrayList;
    21  import java.util.Collection;
    22  import java.util.List;
    23
    24  import org.joda.time.DateTime;
    25  import org.opensaml.common.SAMLObjectBuilder;
    26  import org.opensaml.common.binding.BasicEndpointSelector;
    27  import org.opensaml.common.binding.artifact.SAMLArtifactMap;
    28  import org.opensaml.common.binding.artifact.SAMLArtifactMap.SAMLArtifactMapEntry;
    29  import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
    30  import org.opensaml.common.xml.SAMLConstants;
    31  import org.opensaml.saml1.binding.SAML1ArtifactMessageContext;
    32  import org.opensaml.saml1.core.Assertion;
    33  import org.opensaml.saml1.core.AssertionArtifact;
    34  import org.opensaml.saml1.core.NameIdentifier;
    35  import org.opensaml.saml1.core.Request;
    36  import org.opensaml.saml1.core.Response;
    37  import org.opensaml.saml1.core.Status;
    38  import org.opensaml.saml1.core.StatusCode;
    39  import org.opensaml.saml2.metadata.AssertionConsumerService;
    40  import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
    41  import org.opensaml.saml2.metadata.Endpoint;
    42  import org.opensaml.saml2.metadata.EntityDescriptor;
    43  import org.opensaml.saml2.metadata.SPSSODescriptor;
    44  import org.opensaml.saml2.metadata.provider.MetadataProvider;
    45  import org.opensaml.ws.message.decoder.MessageDecodingException;
    46  import org.opensaml.ws.transport.http.HTTPInTransport;
    47  import org.opensaml.ws.transport.http.HTTPOutTransport;
    48  import org.opensaml.xml.security.SecurityException;
    49  import org.slf4j.Logger;
    50  import org.slf4j.LoggerFactory;
    51
    52  import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
    53  import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
    54  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml1.ArtifactResolutionConfiguration;
    55
    56  /** SAML 1 Artifact resolution profile handler. */
    57  public class ArtifactResolution extends AbstractSAML1ProfileHandler {
    58
    59      /** Class logger. */
    60      private final Logger log = LoggerFactory.getLogger(ArtifactResolution.class);
    61
    62      /** Builder of Response objects. */
    63      private SAMLObjectBuilder<Response> responseBuilder;
    64
    65      /** Builder of assertion consumer service endpoints. */
    66      private SAMLObjectBuilder<AssertionConsumerService> acsEndpointBuilder;
    67
    68      /** Map artifacts to SAML messages. */
    69      private SAMLArtifactMap artifactMap;
    70
    71      /**
    72       * Constructor.
    73       *
    74       * @param map ArtifactMap used to lookup artifacts to be resolved.
    75       */
    76      public ArtifactResolution(SAMLArtifactMap map) {
    77          super();
    78
    79          artifactMap = map;
    80
    81          responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
    82          acsEndpointBuilder = (SAMLObjectBuilder<AssertionConsumerService>) getBuilderFactory().getBuilder(
    83                  AssertionConsumerService.DEFAULT_ELEMENT_NAME);
    84      }
    85
    86      /** {@inheritDoc} */
    87      public String getProfileId() {
    88          return ArtifactResolutionConfiguration.PROFILE_ID;
    89      }
    90
    91      /** {@inheritDoc} */
    92      public void processRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport) throws ProfileException {
    93          Response samlResponse;
    94
    95          ArtifactResolutionRequestContext requestContext = new ArtifactResolutionRequestContext();
    96          decodeRequest(requestContext, inTransport, outTransport);
    97
    98          try {
    99              if (requestContext.getProfileConfiguration() == null) {
    100                 String msg = "SAML 1 Artifact resolution profile is not configured for relying party "
    101                         + requestContext.getInboundMessageIssuer();
    102                 requestContext.setFailureStatus(buildStatus(StatusCode.SUCCESS, StatusCode.REQUEST_DENIED, msg));
    103                 log.warn(msg);
    104                 throw new ProfileException(msg);
    105             }
    106
    107             checkSamlVersion(requestContext);
    108
    109             derferenceArtifacts(requestContext);
    110
    111             // create the SAML response
    112             samlResponse = buildArtifactResponse(requestContext);
    113         } catch (ProfileException e) {
    114             samlResponse = buildErrorResponse(requestContext);
    115         }
    116
    117         requestContext.setOutboundSAMLMessage(samlResponse);
    118         requestContext.setOutboundSAMLMessageId(samlResponse.getID());
    119         requestContext.setOutboundSAMLMessageIssueInstant(samlResponse.getIssueInstant());
    120
    121         encodeResponse(requestContext);
    122         writeAuditLogEntry(requestContext);
    123     }
    124
    125     /**
    126      * Decodes an incoming request and populates a created request context with the resultant information.
    127      *
    128      * @param inTransport inbound message transport
    129      * @param outTransport outbound message transport
    130      * @param requestContext request context to which decoded information should be added
    131      *
    132      * @throws ProfileException throw if there is a problem decoding the request
    133      */
    134     protected void decodeRequest(ArtifactResolutionRequestContext requestContext, HTTPInTransport inTransport,
    135             HTTPOutTransport outTransport) throws ProfileException {
    136         if (log.isDebugEnabled()) {
    137             log.debug("Decoding message with decoder binding '{}'", getInboundMessageDecoder(requestContext)
    138                     .getBindingURI());
    139         }
    140
    141         requestContext.setCommunicationProfileId(getProfileId());
    142
    143         MetadataProvider metadataProvider = getMetadataProvider();
    144         requestContext.setMetadataProvider(metadataProvider);
    145
    146         requestContext.setInboundMessageTransport(inTransport);
    147         requestContext.setInboundSAMLProtocol(SAMLConstants.SAML11P_NS);
    148         requestContext.setSecurityPolicyResolver(getSecurityPolicyResolver());
    149         requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
    150
    151         requestContext.setOutboundMessageTransport(outTransport);
    152         requestContext.setOutboundSAMLProtocol(SAMLConstants.SAML11P_NS);
    153
    154         try {
    155             SAMLMessageDecoder decoder = getInboundMessageDecoder(requestContext);
    156             requestContext.setMessageDecoder(decoder);
    157             decoder.decode(requestContext);
    158             log.debug("Decoded artifact resolution request from relying party '{}'", requestContext
    159                     .getInboundMessageIssuer());
    160         } catch (MessageDecodingException e) {
    161             String msg = "Error decoding artifact resolve message";
    162             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, msg));
    163             log.warn(msg, e);
    164             throw new ProfileException(msg, e);
    165         } catch (SecurityException e) {
    166             String msg = "Message did not meet security requirements";
    167             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED, msg));
    168             log.warn(msg, e);
    169             throw new ProfileException(msg, e);
    170         } finally {
    171             // Set as much information as can be retrieved from the decoded message
    172             populateRequestContext(requestContext);
    173         }
    174     }
    175
    176     /** {@inheritDoc} */
    177     protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
    178             throws ProfileException {
    179         super.populateRelyingPartyInformation(requestContext);
    180
    181         EntityDescriptor relyingPartyMetadata = requestContext.getPeerEntityMetadata();
    182         if (relyingPartyMetadata != null) {
    183             requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
    184             requestContext.setPeerEntityRoleMetadata(relyingPartyMetadata.getSPSSODescriptor(SAMLConstants.SAML11P_NS));
    185         }
    186     }
    187
    188     /** {@inheritDoc} */
    189     protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
    190             throws ProfileException {
    191         super.populateAssertingPartyInformation(requestContext);
    192
    193         EntityDescriptor localEntityDescriptor = requestContext.getLocalEntityMetadata();
    194         if (localEntityDescriptor != null) {
    195             requestContext.setLocalEntityRole(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
    196             requestContext.setLocalEntityRoleMetadata(localEntityDescriptor
    197                     .getAttributeAuthorityDescriptor(SAMLConstants.SAML11P_NS));
    198         }
    199     }
    200
    201     /** {@inheritDoc} */
    202     protected void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
    203         // nothing to do here
    204     }
    205
    206     /**
    207      * Selects the appropriate endpoint for the relying party and stores it in the request context.
    208      *
    209      * @param requestContext current request context
    210      *
    211      * @return Endpoint selected from the information provided in the request context
    212      */
    213     protected Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) {
    214         Endpoint endpoint;
    215
    216         if (getInboundBinding().equals(SAMLConstants.SAML1_SOAP11_BINDING_URI)) {
    217             endpoint = acsEndpointBuilder.buildObject();
    218             endpoint.setBinding(SAMLConstants.SAML1_SOAP11_BINDING_URI);
    219         } else {
    220             BasicEndpointSelector endpointSelector = new BasicEndpointSelector();
    221             endpointSelector.setEndpointType(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
    222             endpointSelector.setMetadataProvider(getMetadataProvider());
    223             endpointSelector.setEntityMetadata(requestContext.getPeerEntityMetadata());
    224             endpointSelector.setEntityRoleMetadata(requestContext.getPeerEntityRoleMetadata());
    225             endpointSelector.setSamlRequest(requestContext.getInboundSAMLMessage());
    226             endpointSelector.getSupportedIssuerBindings().addAll(getSupportedOutboundBindings());
    227             endpoint = endpointSelector.selectEndpoint();
    228         }
    229
    230         return endpoint;
    231     }
    232
    233     /**
    234      * Dereferences the artifacts within the incoming request and stores them in the request context.
    235      *
    236      * @param requestContext current request context
    237      *
    238      * @throws ProfileException thrown if the incoming request does not contain any {@link AssertionArtifact}s.
    239      */
    240     protected void derferenceArtifacts(ArtifactResolutionRequestContext requestContext) throws ProfileException {
    241         Request request = requestContext.getInboundSAMLMessage();
    242         List<AssertionArtifact> assertionArtifacts = request.getAssertionArtifacts();
    243
    244         if (assertionArtifacts == null || assertionArtifacts.size() == 0) {
    245             String msg = "No AssertionArtifacts available in request from relying party "
    246                     + requestContext.getInboundMessageIssuer();
    247             log.warn(msg);
    248             throw new ProfileException(msg);
    249         }
    250
    251         ArrayList<Assertion> assertions = new ArrayList<Assertion>();
    252         SAMLArtifactMapEntry artifactEntry;
    253         for (AssertionArtifact assertionArtifact : assertionArtifacts) {
    254             artifactEntry = artifactMap.get(assertionArtifact.getAssertionArtifact());
    255             if (artifactEntry == null || artifactEntry.isExpired()) {
    256                 log.warn("Unknown AssertionArtifact '{}' from relying party '{}'", assertionArtifact
    257                         .getAssertionArtifact(), requestContext.getInboundMessageIssuer());
    258                 continue;
    259             }
    260
    261             if (!artifactEntry.getIssuerId().equals(requestContext.getLocalEntityId())) {
    262                 log.warn("Artifact issuer mismatch.  Artifact issued by '{}' but IdP has entity ID of '{}'",
    263                         artifactEntry.getIssuerId(), requestContext.getLocalEntityId());
    264                 continue;
    265             }
    266
    267             artifactMap.remove(assertionArtifact.getAssertionArtifact());
    268             assertions.add((Assertion) artifactEntry.getSamlMessage());
    269         }
    270
    271         requestContext.setDereferencedAssertions(assertions);
    272     }
    273
    274     /**
    275      * Builds the response to the artifact request.
    276      *
    277      * @param requestContext current request context
    278      *
    279      * @return response to the artifact request
    280      */
    281     protected Response buildArtifactResponse(ArtifactResolutionRequestContext requestContext) {
    282         DateTime issueInstant = new DateTime();
    283
    284         // create the SAML response and add the assertion
    285         Response samlResponse = responseBuilder.buildObject();
    286         samlResponse.setIssueInstant(issueInstant);
    287         populateStatusResponse(requestContext, samlResponse);
    288
    289         if (requestContext.getDereferencedAssertions() != null) {
    290             samlResponse.getAssertions().addAll(requestContext.getDereferencedAssertions());
    291         }
    292
    293         Status status = buildStatus(StatusCode.SUCCESS, null, null);
    294         samlResponse.setStatus(status);
    295
    296         return samlResponse;
    297     }
    298
    299     /** Represents the internal state of a SAML 1 Artifact resolver request while it's being processed by the IdP. */
    300     public class ArtifactResolutionRequestContext extends
    301             BaseSAML1ProfileRequestContext<Request, Response, ArtifactResolutionConfiguration> implements
    302             SAML1ArtifactMessageContext<Request, Response, NameIdentifier> {
    303
    304         /** Artifact to be resolved. */
    305         private Collection<String> artifacts;
    306
    307         /** Message referenced by the SAML artifact. */
    308         private Collection<Assertion> referencedAssertions;
    309
    310         /** {@inheritDoc} */
    311         public Collection<String> getArtifacts() {
    312             return artifacts;
    313         }
    314
    315         /** {@inheritDoc} */
    316         public void setArtifacts(Collection<String> encodedArtifacts) {
    317             this.artifacts = encodedArtifacts;
    318         }
    319
    320         /**
    321          * Gets the SAML assertions referenced by the artifact(s).
    322          *
    323          * @return SAML assertions referenced by the artifact(s)
    324          */
    325         public Collection<Assertion> getDereferencedAssertions() {
    326             return referencedAssertions;
    327         }
    328
    329         /**
    330          * Sets the SAML assertions referenced by the artifact(s).
    331          *
    332          * @param assertions SAML assertions referenced by the artifact(s)
    333          */
    334         public void setDereferencedAssertions(Collection<Assertion> assertions) {
    335             referencedAssertions = assertions;
    336         }
    337     }
    338 }