View Javadoc

    1   /*
    2    * Copyright 2006 University Corporation for Advanced Internet Development, Inc.
    3    *
    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
    7    *
    8    * http://www.apache.org/licenses/LICENSE-2.0
    9    *
    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.
    15   */
    16
    17  package edu.internet2.middleware.shibboleth.idp.profile.saml2;
    18
    19  import org.joda.time.DateTime;
    20  import org.opensaml.common.SAMLObject;
    21  import org.opensaml.common.SAMLObjectBuilder;
    22  import org.opensaml.common.binding.BasicEndpointSelector;
    23  import org.opensaml.common.binding.artifact.SAMLArtifactMap;
    24  import org.opensaml.common.binding.artifact.SAMLArtifactMap.SAMLArtifactMapEntry;
    25  import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
    26  import org.opensaml.common.xml.SAMLConstants;
    27  import org.opensaml.saml2.binding.SAML2ArtifactMessageContext;
    28  import org.opensaml.saml2.core.ArtifactResolve;
    29  import org.opensaml.saml2.core.ArtifactResponse;
    30  import org.opensaml.saml2.core.NameID;
    31  import org.opensaml.saml2.core.Status;
    32  import org.opensaml.saml2.core.StatusCode;
    33  import org.opensaml.saml2.metadata.AssertionConsumerService;
    34  import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
    35  import org.opensaml.saml2.metadata.Endpoint;
    36  import org.opensaml.saml2.metadata.EntityDescriptor;
    37  import org.opensaml.saml2.metadata.SPSSODescriptor;
    38  import org.opensaml.saml2.metadata.provider.MetadataProvider;
    39  import org.opensaml.ws.message.decoder.MessageDecodingException;
    40  import org.opensaml.ws.transport.http.HTTPInTransport;
    41  import org.opensaml.ws.transport.http.HTTPOutTransport;
    42  import org.opensaml.xml.security.SecurityException;
    43  import org.slf4j.Logger;
    44  import org.slf4j.LoggerFactory;
    45
    46  import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
    47  import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
    48  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.ArtifactResolutionConfiguration;
    49
    50  /**
    51   * SAML 2.0 Artifact resolution profile handler.
    52   */
    53  public class ArtifactResolution extends AbstractSAML2ProfileHandler {
    54
    55      /** Class logger. */
    56      private final Logger log = LoggerFactory.getLogger(ArtifactResolution.class);
    57
    58      /** Map artifacts to SAML messages. */
    59      private SAMLArtifactMap artifactMap;
    60
    61      /** Artifact response object builder. */
    62      private SAMLObjectBuilder<ArtifactResponse> responseBuilder;
    63
    64      /** Builder of assertion consumer service endpoints. */
    65      private SAMLObjectBuilder<AssertionConsumerService> acsEndpointBuilder;
    66
    67      /**
    68       * Constructor.
    69       *
    70       * @param map ArtifactMap used to lookup artifacts to be resolved.
    71       */
    72      public ArtifactResolution(SAMLArtifactMap map) {
    73          super();
    74
    75          artifactMap = map;
    76
    77          responseBuilder = (SAMLObjectBuilder<ArtifactResponse>) getBuilderFactory().getBuilder(
    78                  ArtifactResponse.DEFAULT_ELEMENT_NAME);
    79          acsEndpointBuilder = (SAMLObjectBuilder<AssertionConsumerService>) getBuilderFactory().getBuilder(
    80                  AssertionConsumerService.DEFAULT_ELEMENT_NAME);
    81      }
    82
    83      /** {@inheritDoc} */
    84      public String getProfileId() {
    85          return ArtifactResolutionConfiguration.PROFILE_ID;
    86      }
    87
    88      /** {@inheritDoc} */
    89      public void processRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport) throws ProfileException {
    90          ArtifactResponse samlResponse;
    91
    92          ArtifactResolutionRequestContext requestContext = decodeRequest(inTransport, outTransport);
    93
    94          try {
    95              if (requestContext.getProfileConfiguration() == null) {
    96                  log.error("SAML 2 Artifact Resolve profile is not configured for relying party "
    97                          + requestContext.getInboundMessageIssuer());
    98                  requestContext.setFailureStatus(buildStatus(StatusCode.SUCCESS_URI, StatusCode.REQUEST_DENIED_URI,
    99                          "SAML 2 Artifact Resolve profile is not configured for relying party "
    100                                 + requestContext.getInboundMessageIssuer()));
    101                 throw new ProfileException("SAML 2 Artifact Resolve profile is not configured for relying party "
    102                         + requestContext.getInboundMessageIssuer());
    103             }
    104
    105             checkSamlVersion(requestContext);
    106
    107             SAMLArtifactMapEntry artifactEntry = artifactMap.get(requestContext.getArtifact());
    108             if (artifactEntry == null || artifactEntry.isExpired()) {
    109                 log.error("Unknown artifact.");
    110                 requestContext.setFailureStatus(buildStatus(StatusCode.SUCCESS_URI, StatusCode.REQUEST_DENIED_URI,
    111                         "Unknown artifact."));
    112             }
    113
    114             if (!artifactEntry.getIssuerId().equals(requestContext.getLocalEntityId())) {
    115                 log.error("Artifact issuer mismatch.  Artifact issued by " + artifactEntry.getIssuerId()
    116                         + " but IdP has entity ID of " + requestContext.getLocalEntityId());
    117                 requestContext.setFailureStatus(buildStatus(StatusCode.SUCCESS_URI, StatusCode.REQUEST_DENIED_URI,
    118                         "Artifact issuer mismatch."));
    119             }
    120
    121             if (!artifactEntry.getRelyingPartyId().equals(requestContext.getInboundMessageIssuer())) {
    122                 log.error("Artifact requester mismatch.  Artifact was issued to " + artifactEntry.getRelyingPartyId()
    123                         + " but was resolve request came from " + requestContext.getInboundMessageIssuer());
    124                 requestContext.setFailureStatus(buildStatus(StatusCode.SUCCESS_URI, StatusCode.REQUEST_DENIED_URI,
    125                         "Artifact requester mismatch."));
    126             }
    127
    128             // create the SAML response
    129             requestContext.setReferencedMessage(artifactEntry.getSamlMessage());
    130             samlResponse = buildArtifactResponse(requestContext);
    131         } catch (ProfileException e) {
    132             samlResponse = buildArtifactErrorResponse(requestContext);
    133         }
    134
    135         requestContext.setOutboundSAMLMessage(samlResponse);
    136         requestContext.setOutboundSAMLMessageId(samlResponse.getID());
    137         requestContext.setOutboundSAMLMessageIssueInstant(samlResponse.getIssueInstant());
    138
    139         encodeResponse(requestContext);
    140         writeAuditLogEntry(requestContext);
    141     }
    142
    143     /**
    144      * Decodes an incoming request and populates a created request context with the resultant information.
    145      *
    146      * @param inTransport inbound message transport
    147      * @param outTransport outbound message transport
    148      *
    149      * @return the created request context
    150      *
    151      * @throws ProfileException throw if there is a problem decoding the request
    152      */
    153     protected ArtifactResolutionRequestContext decodeRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport)
    154             throws ProfileException {
    155         log.debug("Decoding message with decoder binding {}", getInboundBinding());
    156
    157         ArtifactResolutionRequestContext requestContext = new ArtifactResolutionRequestContext();
    158         requestContext.setCommunicationProfileId(getProfileId());
    159
    160         MetadataProvider metadataProvider = getMetadataProvider();
    161         requestContext.setMetadataProvider(metadataProvider);
    162
    163         requestContext.setInboundMessageTransport(inTransport);
    164         requestContext.setInboundSAMLProtocol(SAMLConstants.SAML20P_NS);
    165         requestContext.setSecurityPolicyResolver(getSecurityPolicyResolver());
    166         requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
    167
    168         requestContext.setOutboundMessageTransport(outTransport);
    169         requestContext.setOutboundSAMLProtocol(SAMLConstants.SAML20P_NS);
    170
    171         try {
    172             SAMLMessageDecoder decoder = getMessageDecoders().get(getInboundBinding());
    173             requestContext.setMessageDecoder(decoder);
    174             decoder.decode(requestContext);
    175             log.debug("Decoded request");
    176             return requestContext;
    177         } catch (MessageDecodingException e) {
    178             log.error("Error decoding artifact resolve message", e);
    179             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error decoding message"));
    180             throw new ProfileException("Error decoding artifact resolve message");
    181         } catch (SecurityException e) {
    182             log.error("Message did not meet security requirements", e);
    183             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
    184                     "Message did not meet security requirements"));
    185             throw new ProfileException("Message did not meet security requirements", e);
    186         } finally {
    187             populateRequestContext(requestContext);
    188         }
    189     }
    190
    191     /** {@inheritDoc} */
    192     protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
    193             throws ProfileException {
    194         super.populateRelyingPartyInformation(requestContext);
    195
    196         EntityDescriptor relyingPartyMetadata = requestContext.getPeerEntityMetadata();
    197         if (relyingPartyMetadata != null) {
    198             requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
    199             requestContext.setPeerEntityRoleMetadata(relyingPartyMetadata.getSPSSODescriptor(SAMLConstants.SAML20P_NS));
    200         }
    201     }
    202
    203     /** {@inheritDoc} */
    204     protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
    205             throws ProfileException {
    206         super.populateAssertingPartyInformation(requestContext);
    207
    208         EntityDescriptor localEntityDescriptor = requestContext.getLocalEntityMetadata();
    209         if (localEntityDescriptor != null) {
    210             requestContext.setLocalEntityRole(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
    211             requestContext.setLocalEntityRoleMetadata(localEntityDescriptor
    212                     .getAttributeAuthorityDescriptor(SAMLConstants.SAML20P_NS));
    213         }
    214     }
    215
    216     /**
    217      * Populates the request context with information from the inbound SAML message.
    218      *
    219      * This method requires the the following request context properties to be populated: inbound saml message
    220      *
    221      * This methods populates the following request context properties: subject name identifier
    222      *
    223      * @param requestContext current request context
    224      *
    225      * @throws ProfileException thrown if the inbound SAML message or subject identifier is null
    226      */
    227     protected void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
    228         ArtifactResolve samlMessage = (ArtifactResolve) requestContext.getInboundSAMLMessage();
    229         ((ArtifactResolutionRequestContext) requestContext).setArtifact(samlMessage.getArtifact().getArtifact());
    230     }
    231
    232     /**
    233      * Selects the appropriate endpoint for the relying party and stores it in the request context.
    234      *
    235      * @param requestContext current request context
    236      *
    237      * @return Endpoint selected from the information provided in the request context
    238      */
    239     protected Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) {
    240         Endpoint endpoint;
    241
    242         if (getInboundBinding().equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) {
    243             endpoint = acsEndpointBuilder.buildObject();
    244             endpoint.setBinding(SAMLConstants.SAML2_SOAP11_BINDING_URI);
    245         } else {
    246             BasicEndpointSelector endpointSelector = new BasicEndpointSelector();
    247             endpointSelector.setEndpointType(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
    248             endpointSelector.setMetadataProvider(getMetadataProvider());
    249             endpointSelector.setEntityMetadata(requestContext.getPeerEntityMetadata());
    250             endpointSelector.setEntityRoleMetadata(requestContext.getPeerEntityRoleMetadata());
    251             endpointSelector.setSamlRequest(requestContext.getInboundSAMLMessage());
    252             endpointSelector.getSupportedIssuerBindings().addAll(getSupportedOutboundBindings());
    253             endpoint = endpointSelector.selectEndpoint();
    254         }
    255
    256         return endpoint;
    257     }
    258
    259     /**
    260      * Constructs a artifact resolution response with the derferenced SAML message inside.
    261      *
    262      * @param requestContext current request context
    263      *
    264      * @return constructed response
    265      */
    266     protected ArtifactResponse buildArtifactResponse(ArtifactResolutionRequestContext requestContext) {
    267         DateTime issueInstant = new DateTime();
    268
    269         // create the SAML response and add the assertion
    270         ArtifactResponse samlResponse = responseBuilder.buildObject();
    271         samlResponse.setIssueInstant(issueInstant);
    272         populateStatusResponse(requestContext, samlResponse);
    273
    274         if (requestContext.getFailureStatus() == null) {
    275             Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
    276             samlResponse.setStatus(status);
    277             samlResponse.setMessage(requestContext.getReferencedMessage());
    278         } else {
    279             samlResponse.setStatus(requestContext.getFailureStatus());
    280         }
    281
    282         return samlResponse;
    283     }
    284
    285     /**
    286      * Constructs an artifact resolution response with an error status as content.
    287      *
    288      * @param requestContext current request context
    289      *
    290      * @return constructed response
    291      */
    292     protected ArtifactResponse buildArtifactErrorResponse(ArtifactResolutionRequestContext requestContext) {
    293         ArtifactResponse samlResponse = responseBuilder.buildObject();
    294         samlResponse.setIssueInstant(new DateTime());
    295         populateStatusResponse(requestContext, samlResponse);
    296
    297         samlResponse.setStatus(requestContext.getFailureStatus());
    298
    299         return samlResponse;
    300     }
    301
    302     /** Represents the internal state of a SAML 2.0 Artiface resolver request while it's being processed by the IdP. */
    303     public class ArtifactResolutionRequestContext extends
    304             BaseSAML2ProfileRequestContext<ArtifactResolve, ArtifactResponse, ArtifactResolutionConfiguration>
    305             implements SAML2ArtifactMessageContext<ArtifactResolve, ArtifactResponse, NameID> {
    306
    307         /** Artifact to be resolved. */
    308         private String artifact;
    309
    310         /** Message referenced by the SAML artifact. */
    311         private SAMLObject referencedMessage;
    312
    313         /** {@inheritDoc} */
    314         public String getArtifact() {
    315             return artifact;
    316         }
    317
    318         /** {@inheritDoc} */
    319         public void setArtifact(String saml2Artifact) {
    320             this.artifact = saml2Artifact;
    321         }
    322
    323         /** {@inheritDoc} */
    324         public SAMLObject getReferencedMessage() {
    325             return referencedMessage;
    326         }
    327
    328         /** {@inheritDoc} */
    329         public void setReferencedMessage(SAMLObject message) {
    330             referencedMessage = message;
    331         }
    332     }
    333 }