2 * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * AttributeRequestor.java
20 * Generate a SAMLRequest to the AA for Attributes, then process the
21 * reply. In C++, this logic was InternalCCacheEntry::getNewResponse()
24 * The User Authentication was previously processed by ShibPOSTProfile
25 * and was stored by SessionManager in a Session object. Although the
26 * attributes might be fetched immediately, that is a strategy issue.
27 * In Java, we isolate the Attribute fetch in this separate module.
29 * The Session block retains a copy of the original SAMLStatement from
30 * the POST, and it contains information about the remote Entity.
31 * However, the POST came from the HS (or IIDPProvider if you want to
32 * use SAML 2 terms), and this transaction has to go to the AA. So
33 * Metadata must be used to obtain the AttributeAuthorityRole and its
34 * associated Endpoint (URL).
36 * The ApplicationInfo object for the configured Application presents
37 * a getAttributeDesignators method that can return a list of attributes
38 * to specify in the request. However, I can find no configuration element
39 * that corresponds to this, and no example logic. For now that method
40 * returns an empty collection and no particular attributes are requested.
42 * The actual SSL session and exchange of data is performed by OpenSAML.
43 * Our interface to SAML is through the separate ShibBind module. The
44 * layers of processing and responsibilites need to be understood.
46 * ShibBind uses the Metadata for the User's ID Providing Entity to
47 * locate the AttributeAuthorityRole and therefore the AA URL. This
48 * is passed to OpenSAML along with the request. Upon return, if any
49 * statements are signed it is the responsiblilty of ShibBind to call
50 * the Trust implementations to validate the signatures.
52 * This module then checks, by calling the isSigned() property of
53 * SAMLObjects, to make sure that everything that is supposed to be
54 * signed actually was signed. ShibBind knows if a signature is valid,
55 * but this module knows if a signature was requred. This module also
56 * applies AAP to examine attributes and values and discard those that
57 * the policy doesn't accept.
59 * Recovery Context: All exceptions handled and logged internally.
61 package edu.internet2.middleware.shibboleth.serviceprovider;
63 import java.security.Key;
64 import java.security.cert.X509Certificate;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.Collection;
68 import java.util.Iterator;
70 import org.apache.log4j.Logger;
71 import org.apache.xml.security.signature.XMLSignature;
72 import org.opensaml.SAMLAssertion;
73 import org.opensaml.SAMLAttributeQuery;
74 import org.opensaml.SAMLAuthenticationStatement;
75 import org.opensaml.SAMLException;
76 import org.opensaml.SAMLRequest;
77 import org.opensaml.SAMLResponse;
78 import org.opensaml.SAMLSubject;
79 import org.opensaml.XML;
81 import edu.internet2.middleware.shibboleth.common.Credential;
82 import edu.internet2.middleware.shibboleth.common.Credentials;
83 import edu.internet2.middleware.shibboleth.metadata.AttributeAuthorityDescriptor;
84 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
85 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
88 * A static class with a static method. No objects are created
90 * @author Howard Gilbert
92 public class AttributeRequestor {
94 private static Logger log = Logger.getLogger(AttributeRequestor.class);
95 private static ServiceProviderContext context = ServiceProviderContext.getInstance();
97 private AttributeRequestor() {} // Prevent instantiation
100 * Request SAML Attribute response from AA
102 * @param session Session object (from authentication POST)
103 * @return true if Attributes successfully stored in the Session
104 * @throws MetadataException If IdP has no configured AA
105 * @throws SAMLException If there is a problem with the reply
108 boolean // return false if attributes are not fetched
112 log.debug("Fetching attributes for session "+session.getKey()+
113 " from "+session.getEntityId());
115 // Get local references to configuration objects
116 ServiceProviderConfig config = context.getServiceProviderConfig();
117 ApplicationInfo appinfo = config.getApplication(session.getApplicationId());
119 SAMLResponse response = session.getAttributeResponse();
121 // The Entity name was fed by by ShibPOSTProfile.accept(). Look it up in the
122 // Metadata now and return the Entity object.
123 EntityDescriptor entity = appinfo.lookup(session.getEntityId());
125 log.error("Entity(Site) deleted from Metadata since authentication POST received: "+session.getEntityId());
129 SAMLRequest request = null;
131 // Find the Shibboleth protocol AA Role configured in the Metadat for this Entity.
132 AttributeAuthorityDescriptor aa =
133 entity.getAttributeAuthorityDescriptor(XML.SAML11_PROTOCOL_ENUM); // throws MetadataException
135 log.error("No Attribute Authority in Metadata for ID="+entity.getId());
139 if (response==null) {
140 // Build the Attribute Query
141 SAMLAttributeQuery query = null;
144 // Get the POST data from the Session. It has the Subject and its source.
145 SAMLAuthenticationStatement authenticationStatement = session.getAuthenticationStatement();
146 if (authenticationStatement==null) {
147 log.error("Session contains no Authentication Statement." );
150 SAMLSubject subject2 = authenticationStatement.getSubject();
151 if (subject2==null) {
152 log.error("Session Authentication Statement contains no Subject." );
155 subject = (SAMLSubject) subject2.clone();
156 } catch (Exception e) {
157 log.error("Unable to generate the query SAMLSubject from the Authenticaiton." );
160 log.debug("Subject (Handle) is "+subject.getNameIdentifier());
165 Collection attributeDesignators = appinfo.getAttributeDesignators();
168 new SAMLAttributeQuery(
169 subject, // Subject (i.e. Handle) from authentication
170 appinfo.getProviderId(), // SP Entity name
171 attributeDesignators // Attributes to request, null for everything
174 // Wrap the Query in a request
175 request = new SAMLRequest(query);
176 } catch (SAMLException e) {
177 log.error("AttributeRequestor unable to build SAML Query for Session "+session.getKey());
181 String credentialId = appinfo.getCredentialIdForEntity(entity);
182 if (credentialId!=null)
183 possiblySignRequest(config.getCredentials(), request, credentialId);
185 // ShibBinding will extract URLs from the Metadata and build
186 // parameters so SAML can create the session. It also interfaces
187 // to Trust to verify that any signed objects have trusted signatures.
189 ShibBinding binding = new ShibBinding(session.getApplicationId());
190 response = binding.send(request,aa,null,null,appinfo);
191 } catch (SAMLException e) {;} // response will be null
192 if (response==null) {
193 log.error("AttributeRequestor Query to remote AA returned no response from "+session.getEntityId());
197 log.info("Bypassing Attribute Query because Attributes already Pushed.");
200 // Check each assertion in the response.
202 Iterator assertions = response.getAssertions();
203 ArrayList assertionList = new ArrayList();
204 while (assertions.hasNext()) {
205 assertionList.add(assertions.next());
207 assertions=assertionList.iterator();
208 while (assertions.hasNext()) {
209 SAMLAssertion assertion = (SAMLAssertion) assertions.next();
210 // if (signedAssertions && !assertion.isSigned()) {
211 // log.warn("AttributeRequestor has removed unsigned assertion from response from "+session.getEntityId());
212 // response.removeAssertion(acount);
217 appinfo.applyAAP(assertion,aa); // apply each AAP to this assertion
220 catch (SAMLException ex) {
221 response.removeAssertion(acount); // AAP rejected all statements for this assertion
225 // A response may end up with no attributes, but that is not an error.
226 // Maybe there is just nothing important to say about this user.
228 session.setAttributeResponse(response); // Save response in Session object
233 * Given a credentialId from the CredentialUse/RelyingParty stuff,
234 * find a corresponding Credential element and use its Key/Cert to
237 * @oaran credentials Credentials object from config file
238 * @param request SAML AA Query request
239 * @param credentialId Siging Id from CredentialUse
241 static void possiblySignRequest(
242 Credentials credentials,
244 String credentialId) {
247 if (credentials==null) {
248 log.error("No Credentials Element in SP Config file.");
251 Credential credential = credentials.getCredential(credentialId);
252 if (credential==null) {
253 log.error("No credential found for id "+credentialId);
256 Key key = credential.getPrivateKey();
257 X509Certificate[] certificateChain = credential.getX509CertificateChain();
259 request.sign(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1,key,Arrays.asList(certificateChain));
260 log.debug("Attribute Request signed with "+credentialId);
261 } catch (SAMLException e) {
262 log.error("Unable to sign Attribute Request", e);