62e6822619e855e1514c42d3a5496c42b31c5c58
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / AttributeRequestor.java
1 /*
2  * Copyright [2005] [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 /*
18  * AttributeRequestor.java
19  * 
20  * Generate a SAMLRequest to the AA for Attributes, then process the
21  * reply. In C++, this logic was InternalCCacheEntry::getNewResponse() 
22  * in shib-ccache.cpp.
23  * 
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.
28  * 
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).
35  * 
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.
41  * 
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.
45  * 
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.
51  * 
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.
58  * 
59  * Recovery Context: All exceptions handled and logged internally.
60  */
61 package edu.internet2.middleware.shibboleth.serviceprovider;
62
63 import java.security.Key;
64 import java.security.cert.X509Certificate;
65 import java.util.Arrays;
66 import java.util.Collection;
67 import java.util.Iterator;
68
69 import org.apache.log4j.Logger;
70 import org.apache.xml.security.signature.XMLSignature;
71 import org.opensaml.SAMLAssertion;
72 import org.opensaml.SAMLAttributeQuery;
73 import org.opensaml.SAMLAuthenticationStatement;
74 import org.opensaml.SAMLException;
75 import org.opensaml.SAMLRequest;
76 import org.opensaml.SAMLResponse;
77 import org.opensaml.SAMLSubject;
78 import org.opensaml.XML;
79
80 import edu.internet2.middleware.shibboleth.common.Credential;
81 import edu.internet2.middleware.shibboleth.common.Credentials;
82 import edu.internet2.middleware.shibboleth.metadata.AttributeAuthorityDescriptor;
83 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
84 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
85
86 /**
87  * A static class with a static method. No objects are created
88  * 
89  * @author Howard Gilbert
90  */
91 public class AttributeRequestor {
92         
93         private static Logger log = Logger.getLogger(AttributeRequestor.class);
94         private static ServiceProviderContext context   = ServiceProviderContext.getInstance();
95         
96         private AttributeRequestor() {} // Prevent instantiation
97         
98         /**
99          * Request SAML Attribute response from AA
100          * 
101          * @param session Session object (from authentication POST)
102          * @return true if Attributes successfully stored in the Session
103          * @throws MetadataException If IdP has no configured AA
104          * @throws SAMLException If there is a problem with the reply
105          */
106         static 
107                         boolean    // return false if attributes are not fetched
108         fetchAttributes(
109                         Session session){
110                 
111             log.debug("Fetching attributes for session "+session.getKey()+
112                     " from "+session.getEntityId());
113             
114                 // Get local references to configuration objects
115                 ServiceProviderConfig config = context.getServiceProviderConfig();
116                 ApplicationInfo appinfo = config.getApplication(session.getApplicationId());
117                 
118         SAMLResponse response = session.getAttributeResponse();
119
120         // The Entity name was fed by by ShibPOSTProfile.accept(). Look it up in the
121         // Metadata now and return the Entity object.
122                 EntityDescriptor entity = appinfo.lookup(session.getEntityId());
123                 if (entity==null) {
124                         log.error("Entity(Site) deleted from Metadata since authentication POST received: "+session.getEntityId());
125                         return false;
126                 }
127         
128                 SAMLRequest request = null;
129                 
130         // Find the Shibboleth protocol AA Role configured in the Metadat for this Entity. 
131                 AttributeAuthorityDescriptor aa = 
132                     entity.getAttributeAuthorityDescriptor(XML.SAML11_PROTOCOL_ENUM); // throws MetadataException
133                 if (aa==null) {
134                     log.error("No Attribute Authority in Metadata for ID="+entity.getId());
135                     return false;
136                 }
137                 
138                 if (response==null) {
139                     // Build the Attribute Query 
140                     SAMLAttributeQuery query = null;
141                     SAMLSubject subject;
142                     try {
143                         // Get the POST data from the Session. It has the Subject and its source.
144                         SAMLAuthenticationStatement authenticationStatement = session.getAuthenticationStatement();
145                         if (authenticationStatement==null) {
146                             log.error("Session contains no Authentication Statement." );
147                             return false;
148                         }
149                         SAMLSubject subject2 = authenticationStatement.getSubject();
150                         if (subject2==null) {
151                             log.error("Session Authentication Statement contains no Subject." );
152                             return false;
153                         }
154                         subject = (SAMLSubject) subject2.clone();
155                     } catch (Exception e) {
156                         log.error("Unable to generate the query SAMLSubject from the Authenticaiton." );
157                         return false;
158                     }
159                     log.debug("Subject (Handle) is "+subject.getNameIdentifier());
160                     
161                     
162                     
163                     
164                     Collection attributeDesignators = appinfo.getAttributeDesignators();
165                     try {
166                         query = 
167                             new SAMLAttributeQuery(
168                                     subject,                     // Subject (i.e. Handle) from authentication
169                                     appinfo.getProviderId(),  // SP Entity name
170                                     attributeDesignators // Attributes to request, null for everything
171                             );
172                         
173                         // Wrap the Query in a request
174                         request = new SAMLRequest(query);
175                     } catch (SAMLException e) {
176                         log.error("AttributeRequestor unable to build SAML Query for Session "+session.getKey());
177                         return false;
178                     }
179                     
180                     String credentialId = appinfo.getCredentialIdForEntity(entity);
181                     if (credentialId!=null)
182                         possiblySignRequest(config.getCredentials(), request, credentialId);
183                     
184                     // ShibBinding will extract URLs from the Metadata and build
185                     // parameters so SAML can create the session. It also interfaces
186                     // to Trust to verify that any signed objects have trusted signatures.
187                     try {
188                         ShibBinding binding = new ShibBinding(session.getApplicationId());
189                         response = binding.send(request,aa,null,null,appinfo);
190                     } catch (SAMLException e) {;} // response will be null
191                     if (response==null) {
192                         log.error("AttributeRequestor Query to remote AA returned no response from "+session.getEntityId());
193                         return false;
194                     }
195                 }
196                 
197                 // Check each assertion in the response.
198         int acount = 0;
199                 Iterator assertions = response.getAssertions();
200                 while (assertions.hasNext()) {
201                         SAMLAssertion assertion = (SAMLAssertion) assertions.next();
202 //                      if (signedAssertions && !assertion.isSigned()) {
203 //                              log.warn("AttributeRequestor has removed unsigned assertion from response from "+session.getEntityId());
204 //                              response.removeAssertion(acount);
205 //                              continue;
206 //                      }
207                         
208             try {
209                 appinfo.applyAAP(assertion,aa); // apply each AAP to this assertion
210                 acount++;
211             }
212                         catch (SAMLException ex) {
213                 response.removeAssertion(acount); // AAP rejected all statements for this assertion
214             }
215                 }
216
217                 // A response may end up with no attributes, but that is not an error.
218                 // Maybe there is just nothing important to say about this user.
219                 
220                 session.setAttributeResponse(response); // Save response in Session object
221                 return true;
222         }
223
224     /**
225      * Given a credentialId from the CredentialUse/RelyingParty stuff,
226      * find a corresponding Credential element and use its Key/Cert to 
227      * sign the Request. 
228      * 
229      * @oaran credentials Credentials object from config file
230      * @param request SAML AA Query request
231      * @param credentialId Siging Id from CredentialUse
232      */
233         static void possiblySignRequest(
234             Credentials credentials, 
235                 SAMLRequest request,
236                 String credentialId) {
237         
238         
239             if (credentials==null) {
240             log.error("No Credentials Element in SP Config file.");
241                 return;
242         }
243             Credential credential = credentials.getCredential(credentialId);
244             if (credential==null) {
245             log.error("No credential found for id "+credentialId);
246                 return;
247         }
248             Key key = credential.getPrivateKey();
249             X509Certificate[] certificateChain = credential.getX509CertificateChain();
250             try {
251                 request.sign(XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1,key,Arrays.asList(certificateChain));
252                 log.debug("Attribute Request signed with "+credentialId);
253             } catch (SAMLException e) {
254                 log.error("Unable to sign Attribute Request", e);
255             }
256         }
257
258 }