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