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 }