Import cleanups.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / ShibBinding.java
1 /*
2  * ShibBinding.java
3  * 
4  * Corresponds to ShibBinding.cpp
5  * 
6  * A Shibboleth wrapper around the services of SAMLSOAPBinding,
7  * this class adds processing from the Shibboleth configuration 
8  * to the process of sending a SAMLRequest and getting a SAMLResponse.
9  * In particular, the caller of a ShibBinding provides arguments
10  * that identify the target of the request from the Metadata, and
11  * the caller passes an implementation of Trust so that signatures
12  * can be validated.
13  * 
14  * --------------------
15  * Copyright 2002, 2004 
16  * University Corporation for Advanced Internet Development, Inc. 
17  * All rights reserved
18  * [Thats all we have to say to protect ourselves]
19  * Your permission to use this code is governed by "The Shibboleth License".
20  * A copy may be found at http://shibboleth.internet2.edu/license.html
21  * [Nothing in copyright law requires license text in every file.]
22  */
23 package edu.internet2.middleware.shibboleth.serviceprovider;
24
25 import java.util.Iterator;
26
27 import org.apache.log4j.Logger;
28 import org.opensaml.NoSuchProviderException;
29 import org.opensaml.SAMLAssertion;
30 import org.opensaml.SAMLAuthorityBinding;
31 import org.opensaml.SAMLBinding;
32 import org.opensaml.SAMLBindingFactory;
33 import org.opensaml.SAMLException;
34 import org.opensaml.SAMLRequest;
35 import org.opensaml.SAMLResponse;
36 import org.opensaml.TrustException;
37
38 import edu.internet2.middleware.shibboleth.metadata.AttributeAuthorityRole;
39 import edu.internet2.middleware.shibboleth.metadata.Endpoint;
40 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
41
42 /**
43  * Wrapper for a SAMLBinding send/receive operation.
44  * 
45  * <p>A ServiceProvider creates a ShibBinding object and then calls
46  * its send() method. The logic is certainly capable of generating any
47  * SAML Request/Response sequence. However, the variables have been
48  * declared to have more specific types than the general logic, so this
49  * version can only be used by a Service Provider to make an attribute query
50  * to the AA.</p>
51  * 
52  * <p>The ShibBinding doesn't hold any important resources. The
53  * identity of the AA isn't passed until the send() method and could change
54  * across calls, so there aren't any persistent network resources. Nothing
55  * prevents a ShibBinding object from being reused, but normally it is
56  * just a transient object as in resp=(new ShibBinding(appid)).send(req,...)</p>
57  * 
58  * @author Howard Gilbert
59  */
60 public class ShibBinding {
61         
62         private static Logger log = Logger.getLogger(ShibBinding.class);
63         
64         private static ServiceProviderContext context = ServiceProviderContext.getInstance();
65         
66         private String applicationId = null;
67         private SAMLBinding sbinding = null;
68         
69         /**
70          * While the C++ constructor takes iterators over the Trust and 
71          * Metadata, here we provide the key of an ApplicationInfo object
72          * that contains them.
73          * 
74          * @param applicationId
75          * @throws NoSuchProviderException
76          */
77         public 
78         ShibBinding(
79                         String applicationId) throws NoSuchProviderException {
80                 this.applicationId=applicationId;
81         sbinding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
82         }
83
84         /**
85          * Send a SAMLRequest and get back a SAMLResponse.
86          * 
87          * <p>Although this logic could be generalized, this version
88          * declares the arguments to be of specific types (an AA role)
89          * so it can only be used to send the Attribute Query and get back
90          * the Attribute Assertions.
91          * 
92          * @param req        SAMLRequest to send
93          * @param role       AttributeAuthorityRole representing destination
94          * @param audiences  Audience strings to check SAML conditions
95          * @param bindings   Stupid idea. Don't use this parameter
96          * @return           The SAMLResponse
97          * @throws SAMLException
98          */
99         public 
100                         SAMLResponse 
101         send (
102                         SAMLRequest req,
103                         AttributeAuthorityRole role,
104                         String[] audiences,
105                         SAMLAuthorityBinding[] bindings) 
106         throws SAMLException {
107                 
108                 // For the duration of the request, get local references to
109                 // configuration objects that might change between requests
110                 ServiceProviderConfig config = context.getServiceProviderConfig();
111                 ApplicationInfo appinfo = config.getApplication(applicationId);
112                 
113                 SAMLResponse resp = null;
114                 String prevBinding = null;
115         
116                 /*
117                  * I seriously considered commenting this block out. It makes
118                  * no particular sense for the caller to know about or provide
119                  * SAMLAuthorityBinding objects. In any rational world, 
120                  * a caller inside Shibboleth is going to represent the 
121                  * AA from the Metadata. 
122                  */
123                 if (bindings!=null) {
124                         for (int ibinding=0;ibinding<bindings.length;ibinding++) {
125                                 try {
126                                         SAMLAuthorityBinding binding = bindings[ibinding];
127                                         String bindingString = binding.getBinding();
128                                         if (!bindingString.equals(prevBinding)) {
129                                                 prevBinding = bindingString;
130                                                 resp=sbinding.send(binding.getLocation(),req);
131                                         }
132                                         validateResponseSignatures(role, appinfo, resp);
133                                         return resp;
134                                 } catch (SAMLException e) {
135                                         continue;
136                                 }
137                         }
138                 }
139                 
140                 /*
141                  * In concept, a Role can have a collection of Endpoints.
142                  * The theory is that SAML 2.0 Metadata might have different
143                  * entries for different protocols (or different versions of
144                  * the same protocol).
145                  * The current Shibboleth configuration file doesn't allow this.
146                  * Later on, when support for SAML 2.0 metadata is added, it is
147                  * just as likely that the Endpoint array would be filtered by
148                  * the configuration construction/parse process to leave only
149                  * relevant entries.
150                  * So for now, the C++ code to run the array and filter entries
151                  * is replaced by logic that "knows" there is exactly one 
152                  * Endpoint per Role (built into the XMLProviderRoleImpl).
153                  */
154                 Endpoint[] ends = role.getAttributeServices();
155                 Endpoint endpoint = ends[0];
156                 
157                 log.debug("AA is at "+endpoint.getLocation());
158                 
159                 try {
160                         resp=sbinding.send(endpoint.getLocation(),req);
161                         log.debug("AA returned Attribute Assertion");
162                         validateResponseSignatures(role, appinfo, resp);
163                         return resp;
164                 } catch (TrustException e) {
165                         log.error("Unable to validate signatures on attribute request",e);
166                         throw e;
167                 } catch (SAMLException e) {
168                         log.error("Unable to query attributes.",e);
169                         throw e;
170                 }
171         }
172
173         /**
174          * Validate signatures in response against the Trust configuration.
175          * 
176          * @param role     OriginSite
177          * @param appinfo  Application data
178          * @param resp     SAML response
179          * @throws TrustException on failure
180          */
181         private void 
182         validateResponseSignatures(
183                         AttributeAuthorityRole role, 
184                         ApplicationInfo appinfo, 
185                         SAMLResponse resp) 
186         throws TrustException {
187                 
188                 if (resp.isSigned()&& !appinfo.validate(role,resp)) {
189                         throw new TrustException("Unable to validate signature of response");
190                 }
191                 
192                 Iterator assertions = resp.getAssertions();
193                 while (assertions.hasNext()) {
194                         SAMLAssertion assertion = (SAMLAssertion) assertions.next();
195                         
196                         // TODO Dropped some logic validating conditions
197                         
198                         if (assertion.isSigned() && 
199                                 !appinfo.validate(role,assertion)) {
200                                 throw new TrustException("Unable to validate signature of assertion in response");
201                         }
202                 }
203         }
204 }