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