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
    22  import org.opensaml.common.SAMLObjectBuilder;
    23  import org.opensaml.common.binding.BasicEndpointSelector;
    24  import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
    25  import org.opensaml.common.xml.SAMLConstants;
    26  import org.opensaml.saml1.core.AttributeQuery;
    27  import org.opensaml.saml1.core.AttributeStatement;
    28  import org.opensaml.saml1.core.NameIdentifier;
    29  import org.opensaml.saml1.core.Request;
    30  import org.opensaml.saml1.core.Response;
    31  import org.opensaml.saml1.core.Statement;
    32  import org.opensaml.saml1.core.StatusCode;
    33  import org.opensaml.saml1.core.Subject;
    34  import org.opensaml.saml2.metadata.AssertionConsumerService;
    35  import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
    36  import org.opensaml.saml2.metadata.Endpoint;
    37  import org.opensaml.saml2.metadata.EntityDescriptor;
    38  import org.opensaml.saml2.metadata.SPSSODescriptor;
    39  import org.opensaml.saml2.metadata.provider.MetadataProvider;
    40  import org.opensaml.ws.message.decoder.MessageDecodingException;
    41  import org.opensaml.ws.transport.http.HTTPInTransport;
    42  import org.opensaml.ws.transport.http.HTTPOutTransport;
    43  import org.opensaml.xml.security.SecurityException;
    44  import org.slf4j.Logger;
    45  import org.slf4j.LoggerFactory;
    46
    47  import edu.internet2.middleware.shibboleth.common.attribute.provider.BasicAttribute;
    48  import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
    49  import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
    50  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml1.AttributeQueryConfiguration;
    51  import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
    52  import edu.internet2.middleware.shibboleth.idp.session.Session;
    53
    54  /**
    55   * SAML 1 Attribute Query profile handler.
    56   */
    57  public class AttributeQueryProfileHandler extends AbstractSAML1ProfileHandler {
    58
    59      /** Class logger. */
    60      private final Logger log = LoggerFactory.getLogger(AttributeQueryProfileHandler.class);
    61
    62      /** Builder of NameIdentifier objects. */
    63      private SAMLObjectBuilder<NameIdentifier> nameIdentifierBuilder;
    64
    65      /** Builder of assertion consumer service endpoints. */
    66      private SAMLObjectBuilder<AssertionConsumerService> acsEndpointBuilder;
    67
    68      /** Constructor. */
    69      public AttributeQueryProfileHandler() {
    70          super();
    71
    72          nameIdentifierBuilder = (SAMLObjectBuilder<NameIdentifier>) getBuilderFactory().getBuilder(
    73                  NameIdentifier.DEFAULT_ELEMENT_NAME);
    74          acsEndpointBuilder = (SAMLObjectBuilder<AssertionConsumerService>) getBuilderFactory().getBuilder(
    75                  AssertionConsumerService.DEFAULT_ELEMENT_NAME);
    76      }
    77
    78      /** {@inheritDoc} */
    79      public String getProfileId() {
    80          return AttributeQueryConfiguration.PROFILE_ID;
    81      }
    82
    83      /** {@inheritDoc} */
    84      public void processRequest(HTTPInTransport inTransport, HTTPOutTransport outTransport) throws ProfileException {
    85          AttributeQueryContext requestContext = new AttributeQueryContext();
    86          Response samlResponse;
    87          try {
    88              decodeRequest(requestContext, inTransport, outTransport);
    89
    90              if (requestContext.getProfileConfiguration() == null) {
    91                  log.error("SAML 1 Attribute Query profile is not configured for relying party "
    92                          + requestContext.getInboundMessageIssuer());
    93                  requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED,
    94                          "SAML 1 Attribute Query profile is not configured for relying party "
    95                                  + requestContext.getInboundMessageIssuer()));
    96                  samlResponse = buildErrorResponse(requestContext);
    97              } else {
    98                  resolvePrincipal(requestContext);
    99
    100                 Session idpSession = getSessionManager().getSession(requestContext.getPrincipalName());
    101                 if (idpSession != null) {
    102                     requestContext.setUserSession(idpSession);
    103                     AuthenticationMethodInformation authnInfo = idpSession.getAuthenticationMethods().get(
    104                             requestContext.getInboundMessageIssuer());
    105                     if (authnInfo != null) {
    106                         requestContext.setPrincipalAuthenticationMethod(authnInfo.getAuthenticationMethod());
    107                     }
    108                 }
    109
    110                 resolveAttributes(requestContext);
    111
    112                 ArrayList<Statement> statements = new ArrayList<Statement>();
    113                 AttributeStatement attributeStatement = buildAttributeStatement(requestContext,
    114                         "urn:oasis:names:tc:SAML:1.0:cm:sender-vouches");
    115                 if (attributeStatement != null) {
    116                     requestContext.setReleasedAttributes(requestContext.getAttributes().keySet());
    117                     statements.add(attributeStatement);
    118                 }
    119
    120                 samlResponse = buildResponse(requestContext, statements);
    121             }
    122         } catch (ProfileException e) {
    123             samlResponse = buildErrorResponse(requestContext);
    124         }
    125
    126         requestContext.setOutboundSAMLMessage(samlResponse);
    127         requestContext.setOutboundSAMLMessageId(samlResponse.getID());
    128         requestContext.setOutboundSAMLMessageIssueInstant(samlResponse.getIssueInstant());
    129         encodeResponse(requestContext);
    130         writeAuditLogEntry(requestContext);
    131     }
    132
    133     /**
    134      * Decodes an incoming request and populates a created request context with the resultant information.
    135      *
    136      * @param inTransport inbound message transport
    137      * @param outTransport outbound message transport
    138      * @param requestContext the request context to which decoded information should be added
    139      *
    140      * @throws ProfileException throw if there is a problem decoding the request
    141      */
    142     protected void decodeRequest(AttributeQueryContext requestContext, HTTPInTransport inTransport,
    143             HTTPOutTransport outTransport) throws ProfileException {
    144         if (log.isDebugEnabled()) {
    145             log.debug("Decoding message with decoder binding {}",
    146                     getInboundMessageDecoder(requestContext).getBindingURI());
    147         }
    148
    149         requestContext.setCommunicationProfileId(getProfileId());
    150
    151         MetadataProvider metadataProvider = getMetadataProvider();
    152         requestContext.setMetadataProvider(metadataProvider);
    153
    154         requestContext.setInboundMessageTransport(inTransport);
    155         requestContext.setInboundSAMLProtocol(SAMLConstants.SAML11P_NS);
    156         requestContext.setSecurityPolicyResolver(getSecurityPolicyResolver());
    157         requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
    158
    159         requestContext.setOutboundMessageTransport(outTransport);
    160         requestContext.setOutboundSAMLProtocol(SAMLConstants.SAML11P_NS);
    161
    162         try {
    163             SAMLMessageDecoder decoder = getInboundMessageDecoder(requestContext);
    164             requestContext.setMessageDecoder(decoder);
    165             decoder.decode(requestContext);
    166             log.debug("Decoded request");
    167
    168             Request request = requestContext.getInboundSAMLMessage();
    169             if (request == null || !(request instanceof Request) || request.getAttributeQuery() == null) {
    170                 log.error("Incoming message was not an Attribute request");
    171                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, null,
    172                         "Invalid SAML Attribute Request message."));
    173                 throw new ProfileException("Invalid SAML Attribute Request message.");
    174             }
    175         } catch (MessageDecodingException e) {
    176             log.warn("Error decoding attribute query message", e);
    177             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Error decoding message"));
    178             throw new ProfileException("Error decoding attribute query message", e);
    179         } catch (SecurityException e) {
    180             log.warn("Message did not meet security requirements", e);
    181             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED,
    182                     "Message did not meet security requirements"));
    183             throw new ProfileException("Message did not meet security policy requirements", e);
    184         } finally {
    185             // Set as much information as can be retrieved from the decoded message
    186             populateRequestContext(requestContext);
    187         }
    188     }
    189
    190     /** {@inheritDoc} */
    191     protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
    192             throws ProfileException {
    193         super.populateRelyingPartyInformation(requestContext);
    194
    195         EntityDescriptor relyingPartyMetadata = requestContext.getPeerEntityMetadata();
    196         if (relyingPartyMetadata != null) {
    197             requestContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
    198             requestContext.setPeerEntityRoleMetadata(relyingPartyMetadata.getSPSSODescriptor(SAMLConstants.SAML11P_NS));
    199         }
    200     }
    201
    202     /** {@inheritDoc} */
    203     protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
    204             throws ProfileException {
    205         super.populateAssertingPartyInformation(requestContext);
    206
    207         EntityDescriptor localEntityDescriptor = requestContext.getLocalEntityMetadata();
    208         if (localEntityDescriptor != null) {
    209             requestContext.setLocalEntityRole(AttributeAuthorityDescriptor.DEFAULT_ELEMENT_NAME);
    210             requestContext.setLocalEntityRoleMetadata(localEntityDescriptor
    211                     .getAttributeAuthorityDescriptor(SAMLConstants.SAML11P_NS));
    212         }
    213     }
    214
    215     /**
    216      * Populates the request context with information from the inbound SAML message.
    217      *
    218      * This method requires the the following request context properties to be populated: inbound saml message
    219      *
    220      * This methods populates the following request context properties: subject name identifier
    221      *
    222      * @param requestContext current request context
    223      *
    224      * @throws ProfileException thrown if the inbound SAML message or subject identifier is null
    225      */
    226     protected void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
    227         Request request = (Request) requestContext.getInboundSAMLMessage();
    228         if (request == null) {
    229             log.error("Decoder did not contain an attribute query, an error occured decoding the message");
    230             throw new ProfileException("Unable to decode message.");
    231         }
    232
    233         AttributeQuery query = request.getAttributeQuery();
    234         if (query != null) {
    235             Subject subject = query.getSubject();
    236             if (subject == null) {
    237                 log.error("Attribute query did not contain a proper subject");
    238                 ((AttributeQueryContext) requestContext).setFailureStatus(buildStatus(StatusCode.REQUESTER, null,
    239                         "Attribute query did not contain a proper subject"));
    240                 throw new ProfileException("Attribute query did not contain a proper subject");
    241             }
    242             requestContext.setSubjectNameIdentifier(subject.getNameIdentifier());
    243         }
    244     }
    245
    246     /**
    247      * Selects the appropriate endpoint for the relying party and stores it in the request context.
    248      *
    249      * @param requestContext current request context
    250      *
    251      * @return Endpoint selected from the information provided in the request context
    252      */
    253     protected Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) {
    254         Endpoint endpoint;
    255
    256         if (getInboundBinding().equals(SAMLConstants.SAML1_SOAP11_BINDING_URI)) {
    257             endpoint = acsEndpointBuilder.buildObject();
    258             endpoint.setBinding(SAMLConstants.SAML1_SOAP11_BINDING_URI);
    259         } else {
    260             BasicEndpointSelector endpointSelector = new BasicEndpointSelector();
    261             endpointSelector.setEndpointType(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
    262             endpointSelector.setMetadataProvider(getMetadataProvider());
    263             endpointSelector.setEntityMetadata(requestContext.getPeerEntityMetadata());
    264             endpointSelector.setEntityRoleMetadata(requestContext.getPeerEntityRoleMetadata());
    265             endpointSelector.setSamlRequest(requestContext.getInboundSAMLMessage());
    266             endpointSelector.getSupportedIssuerBindings().addAll(getSupportedOutboundBindings());
    267             endpoint = endpointSelector.selectEndpoint();
    268         }
    269
    270         return endpoint;
    271     }
    272
    273     /** {@inheritDoc} */
    274     protected NameIdentifier buildNameId(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext)
    275         throws ProfileException {
    276
    277         log.debug("Reusing NameIdentifier supplied in query");
    278         NameIdentifier src = requestContext.getSubjectNameIdentifier();
    279         if (src != null) {
    280             NameIdentifier dest = nameIdentifierBuilder.buildObject();
    281             dest.setNameIdentifier(src.getNameIdentifier());
    282             dest.setNameQualifier(src.getNameQualifier());
    283             dest.setFormat(src.getFormat());
    284
    285             if (dest.getNameIdentifier() != null) {
    286                 // TODO: this is a hack to satisfy the audit log, but we should fix the
    287                 // context API to handle the NameID value directly
    288                 BasicAttribute<String> attribute = new BasicAttribute<String>();
    289                 attribute.setId("outboundQueryNameIdentifier");
    290                 attribute.getValues().add(dest.getNameIdentifier());
    291                 requestContext.setNameIdentifierAttribute(attribute);
    292             }
    293
    294             return dest;
    295         }
    296         return null;
    297     }
    298
    299     /** Basic data structure used to accumulate information as a request is being processed. */
    300     protected class AttributeQueryContext extends
    301             BaseSAML1ProfileRequestContext<Request, Response, AttributeQueryConfiguration> {}
    302 }