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