cleanup, add checks to arguments and responses, add log statements
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / ShibBinding.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  * ShibBinding.java
19  * 
20  * Corresponds to ShibBinding.cpp
21  * 
22  * A Shibboleth wrapper around the services of SAMLSOAPBinding,
23  * this class adds processing from the Shibboleth configuration 
24  * to the process of sending a SAMLRequest and getting a SAMLResponse.
25  * In particular, the caller of a ShibBinding provides arguments
26  * that identify the target of the request from the Metadata, and
27  * the caller passes an implementation of Trust so that signatures
28  * can be validated.
29  */
30 package edu.internet2.middleware.shibboleth.serviceprovider;
31
32 import java.util.Iterator;
33
34 import org.apache.log4j.Logger;
35 import org.opensaml.NoSuchProviderException;
36 import org.opensaml.SAMLAssertion;
37 import org.opensaml.SAMLAuthorityBinding;
38 import org.opensaml.SAMLBinding;
39 import org.opensaml.SAMLBindingFactory;
40 import org.opensaml.SAMLCondition;
41 import org.opensaml.SAMLException;
42 import org.opensaml.SAMLRequest;
43 import org.opensaml.SAMLResponse;
44 import org.opensaml.SAMLSOAPHTTPBinding;
45 import org.opensaml.TrustException;
46 import edu.internet2.middleware.shibboleth.common.Trust;
47 import edu.internet2.middleware.shibboleth.metadata.AttributeAuthorityDescriptor;
48 import edu.internet2.middleware.shibboleth.metadata.Endpoint;
49 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
50
51 /**
52  * Wrapper for a SAMLBinding send/receive operation.
53  * 
54  * <p>A ServiceProvider creates a ShibBinding object and then calls
55  * its send() method. The logic is certainly capable of generating any
56  * SAML Request/Response sequence. However, the variables have been
57  * declared to have more specific types than the general logic, so this
58  * version can only be used by a Service Provider to make an attribute query
59  * to the AA.</p>
60  * 
61  * <p>The ShibBinding doesn't hold any important resources. The
62  * identity of the AA isn't passed until the send() method and could change
63  * across calls, so there aren't any persistent network resources. Nothing
64  * prevents a ShibBinding object from being reused, but normally it is
65  * just a transient object as in resp=(new ShibBinding(appid)).send(req,...)</p>
66  * 
67  * @author Howard Gilbert
68  */
69 public class ShibBinding {
70         
71         private static Logger log = Logger.getLogger(ShibBinding.class);
72         
73         private static ServiceProviderContext context = ServiceProviderContext.getInstance();
74         
75         private String applicationId = null;
76         
77         /**
78          * While the C++ constructor takes iterators over the Trust and 
79          * Metadata, here we provide the key of an ApplicationInfo object
80          * that contains them.
81          * 
82          * @param applicationId
83          * @throws NoSuchProviderException
84          */
85         public 
86         ShibBinding(
87                         String applicationId)  {
88                 this.applicationId=applicationId;
89         }
90
91         /**
92          * Send a SAMLRequest and get back a SAMLResponse.
93          * 
94          * <p>Although this logic could be generalized, this version
95          * declares the arguments to be of specific types (an AA role)
96          * so it can only be used to send the Attribute Query and get back
97          * the Attribute Assertions.
98          * 
99          * @param req        SAMLRequest to send
100          * @param role       AttributeAuthorityRole representing destination
101          * @param audiences  Audience strings to check SAML conditions
102          * @param bindings   Stupid idea. Don't use this parameter
103          * @return           The SAMLResponse
104          * @throws SAMLException
105          */
106         public 
107                         SAMLResponse 
108         send (
109                         SAMLRequest req,
110                         AttributeAuthorityDescriptor role,
111                         String[] audiences,
112                         SAMLAuthorityBinding[] bindings,
113             Trust trust) 
114         throws SAMLException {
115                 
116                 // For the duration of the request, get local references to
117                 // configuration objects that might change between requests
118                 ServiceProviderConfig config = context.getServiceProviderConfig();
119                 ApplicationInfo appinfo = config.getApplication(applicationId);
120                 
121         SAMLBinding sbinding = null;
122                 SAMLResponse resp = null;
123                 String prevBinding = null;
124         
125                 /*
126                  * Try any inline bindings provided by 1.0/1.1 IdPs. 
127                  */
128                 if (bindings!=null) {
129                         for (int ibinding=0;ibinding<bindings.length;ibinding++) {
130                                 try {
131                                         SAMLAuthorityBinding binding = bindings[ibinding];
132                                         if (!binding.getBinding().equals(prevBinding)) {
133                                                 prevBinding = binding.getBinding();
134                         sbinding = SAMLBindingFactory.getInstance(binding.getBinding());
135                     }
136                                         resp=sbinding.send(binding.getLocation(),req);
137                                         validateResponseSignatures(role, appinfo, resp);
138                                         return resp;
139                 } catch (TrustException e) {
140                     log.error("Unable to validate signatures on attribute response: " + e);
141                     continue;
142                 } catch (SAMLException e) {
143                     log.error("Unable to query attributes: " + e);
144                     continue;
145                 }
146                         }
147                 }
148                 
149                 /*
150                  * Try each metadata endpoint...
151                  */
152                 Iterator ends = role.getAttributeServiceManager().getEndpoints();
153         while (ends.hasNext()) {
154             Endpoint endpoint = (Endpoint)ends.next();
155             try {
156                 if (!endpoint.getBinding().equals(prevBinding)) {
157                     prevBinding = endpoint.getBinding();
158                     sbinding = SAMLBindingFactory.getInstance(endpoint.getBinding());
159                 }
160                 if (sbinding instanceof SAMLSOAPHTTPBinding) {
161                     SAMLSOAPHTTPBinding httpbind = (SAMLSOAPHTTPBinding)sbinding;
162                     httpbind.addHook(new ShibHttpHook(role,trust));
163                 }
164                 resp=sbinding.send(endpoint.getLocation(),req);
165                 validateResponseSignatures(role, appinfo, resp);
166                 return resp;
167             } catch (TrustException e) {
168                 log.error("Unable to validate signatures on attribute response: " + e);
169                 continue;
170             } catch (SAMLException e) {
171                 log.error("Unable to query attributes: " + e);
172                 continue;
173             }
174         }
175         return null;
176         }
177
178         /**
179          * Validate signatures in response against the Trust configuration.
180          * 
181          * @param role     OriginSite
182          * @param appinfo  Application data
183          * @param resp     SAML response
184          * @throws TrustException on failure
185          */
186         private void 
187         validateResponseSignatures(
188                         AttributeAuthorityDescriptor role, 
189                         ApplicationInfo appinfo, 
190                         SAMLResponse resp) 
191         throws TrustException {
192                 
193                 if (resp.isSigned()&& !appinfo.validate(resp,role)) {
194                         throw new TrustException("Unable to validate signature of response");
195                 }
196                 
197                 Iterator assertions = resp.getAssertions();
198                 while (assertions.hasNext()) {
199                         SAMLAssertion assertion = (SAMLAssertion) assertions.next();
200                         
201                         Iterator conditions = assertion.getConditions();
202                         while (conditions.hasNext()) {
203                                 SAMLCondition condition = (SAMLCondition) conditions.next();
204                                 // TODO C++ only seems to validate that the audience string is present
205                         }
206                         
207                         if (assertion.isSigned() && 
208                                 !appinfo.validate(assertion,role)) {
209                                 throw new TrustException("Unable to validate signature of assertion in response");
210                         }
211                 }
212         }
213 }