Refactorings on the Trust code. (Extracted an interface and added javadoc.)
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPProtocolSupport.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4  * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5  * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
6  * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
7  * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
8  * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu> Internet2 Project.
9  * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10  * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
11  * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12  * products derived from this software without specific prior written permission. For written permission, please contact
13  * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
14  * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
15  * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
16  * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
18  * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
19  * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 package edu.internet2.middleware.shibboleth.idp;
27
28 import java.net.URI;
29 import java.net.URL;
30 import java.security.Principal;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Iterator;
34
35 import org.apache.log4j.Logger;
36 import org.apache.xml.security.signature.XMLSignature;
37 import org.opensaml.InvalidCryptoException;
38 import org.opensaml.SAMLAssertion;
39 import org.opensaml.SAMLAttribute;
40 import org.opensaml.SAMLException;
41 import org.opensaml.SAMLResponse;
42 import org.opensaml.artifact.Artifact;
43 import org.w3c.dom.Element;
44
45 import edu.internet2.middleware.shibboleth.aa.AAAttribute;
46 import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
47 import edu.internet2.middleware.shibboleth.aa.AAException;
48 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
49 import edu.internet2.middleware.shibboleth.aa.arp.ArpProcessingException;
50 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
51 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
52 import edu.internet2.middleware.shibboleth.common.Credential;
53 import edu.internet2.middleware.shibboleth.common.NameMapper;
54 import edu.internet2.middleware.shibboleth.common.RelyingParty;
55 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
56 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
57 import edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrust;
58 import edu.internet2.middleware.shibboleth.common.Trust;
59 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
60 import edu.internet2.middleware.shibboleth.metadata.Metadata;
61 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
62
63 /**
64  * Delivers core IdP functionality (Attribute resolution, ARP filtering, Metadata lookup, Signing, Mapping between local &
65  * SAML identifiers, etc.) to components that process protocol-specific requests.
66  * 
67  * @author Walter Hoehn
68  */
69 public class IdPProtocolSupport implements Metadata {
70
71         private static Logger log = Logger.getLogger(IdPProtocolSupport.class.getName());
72         private Logger transactionLog;
73         private IdPConfig config;
74         private ArrayList fedMetadata = new ArrayList();
75         private NameMapper nameMapper;
76         private ServiceProviderMapper spMapper;
77         private ArpEngine arpEngine;
78         private AttributeResolver resolver;
79         private ArtifactMapper artifactMapper;
80         private Semaphore throttle;
81         private Trust trust = new ShibbolethTrust();
82
83         IdPProtocolSupport(IdPConfig config, Logger transactionLog, NameMapper nameMapper, ServiceProviderMapper spMapper,
84                         ArpEngine arpEngine, AttributeResolver resolver, ArtifactMapper artifactMapper)
85                         throws ShibbolethConfigurationException {
86
87                 this.transactionLog = transactionLog;
88                 this.config = config;
89                 this.nameMapper = nameMapper;
90                 this.spMapper = spMapper;
91                 spMapper.setMetadata(this);
92                 this.arpEngine = arpEngine;
93                 this.resolver = resolver;
94                 this.artifactMapper = artifactMapper;
95
96                 // Load a semaphore that throttles how many requests the IdP will handle at once
97                 throttle = new Semaphore(config.getMaxThreads());
98         }
99
100         public Logger getTransactionLog() {
101
102                 return transactionLog;
103         }
104
105         public IdPConfig getIdPConfig() {
106
107                 return config;
108         }
109
110         public NameMapper getNameMapper() {
111
112                 return nameMapper;
113         }
114
115         public ServiceProviderMapper getServiceProviderMapper() {
116
117                 return spMapper;
118         }
119
120         public void signAssertions(SAMLAssertion[] assertions, RelyingParty relyingParty) throws InvalidCryptoException,
121                         SAMLException {
122
123                 if (relyingParty.getIdentityProvider().getSigningCredential() == null
124                                 || relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() == null) { throw new InvalidCryptoException(
125                                 SAMLException.RESPONDER, "Invalid signing credential."); }
126
127                 for (int i = 0; i < assertions.length; i++) {
128                         String assertionAlgorithm;
129                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
130                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
131                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
132                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
133                         } else {
134                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
135                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
136                         }
137
138                         try {
139                                 throttle.enter();
140                                 assertions[i].sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
141                                                 .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
142                                                 .getX509CertificateChain()));
143                         } finally {
144                                 throttle.exit();
145                         }
146                 }
147         }
148
149         public void signResponse(SAMLResponse response, RelyingParty relyingParty) throws SAMLException {
150
151                 // Make sure we have an appropriate credential
152                 if (relyingParty.getIdentityProvider().getSigningCredential() == null
153                                 || relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() == null) { throw new InvalidCryptoException(
154                                 SAMLException.RESPONDER, "Invalid signing credential."); }
155
156                 // Sign the response
157                 String responseAlgorithm;
158                 if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
159                         responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
160                 } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
161                         responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
162                 } else {
163                         throw new InvalidCryptoException(SAMLException.RESPONDER,
164                                         "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
165                 }
166                 try {
167                         throttle.enter();
168                         response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
169                                         Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
170                 } finally {
171                         throttle.exit();
172                 }
173         }
174
175         protected void addFederationProvider(Element element) {
176
177                 log.debug("Found Federation Provider configuration element.");
178                 if (!element.getTagName().equals("FederationProvider")) {
179                         log.error("Error while attemtping to load Federation Provider.  Malformed provider specificaion.");
180                         return;
181                 }
182
183                 try {
184                         fedMetadata.add(FederationProviderFactory.loadProvider(element));
185                 } catch (MetadataException e) {
186                         log.error("Unable to load Federation Provider.  Skipping...");
187                 }
188         }
189
190         public int providerCount() {
191
192                 return fedMetadata.size();
193         }
194
195         public EntityDescriptor lookup(String providerId) {
196
197                 Iterator iterator = fedMetadata.iterator();
198                 while (iterator.hasNext()) {
199                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(providerId);
200                         if (provider != null) { return provider; }
201                 }
202                 return null;
203         }
204
205         public EntityDescriptor lookup(Artifact artifact) {
206
207                 Iterator iterator = fedMetadata.iterator();
208                 while (iterator.hasNext()) {
209                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(artifact);
210                         if (provider != null) { return provider; }
211                 }
212                 return null;
213         }
214
215         public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource) throws AAException {
216
217                 try {
218                         URI[] potentialAttributes = arpEngine.listPossibleReleaseAttributes(principal, requester, resource);
219                         return getReleaseAttributes(principal, requester, resource, potentialAttributes);
220
221                 } catch (ArpProcessingException e) {
222                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
223                                         + e.getMessage());
224                         throw new AAException("Error retrieving data for principal.");
225                 }
226         }
227
228         public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource,
229                         URI[] attributeNames) throws AAException {
230
231                 try {
232                         AAAttributeSet attributeSet = new AAAttributeSet();
233                         for (int i = 0; i < attributeNames.length; i++) {
234                                 AAAttribute attribute = new AAAttribute(attributeNames[i].toString());
235                                 attributeSet.add(attribute);
236                         }
237
238                         return resolveAttributes(principal, requester, resource, attributeSet);
239
240                 } catch (SAMLException e) {
241                         log.error("An error occurred while creating attributes for principal (" + principal.getName() + ") :"
242                                         + e.getMessage());
243                         throw new AAException("Error retrieving data for principal.");
244
245                 } catch (ArpProcessingException e) {
246                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
247                                         + e.getMessage());
248                         throw new AAException("Error retrieving data for principal.");
249                 }
250         }
251
252         private SAMLAttribute[] resolveAttributes(Principal principal, String requester, URL resource,
253                         AAAttributeSet attributeSet) throws ArpProcessingException {
254
255                 resolver.resolveAttributes(principal, requester, attributeSet);
256                 arpEngine.filterAttributes(attributeSet, principal, requester, resource);
257                 return attributeSet.getAttributes();
258         }
259
260         /**
261          * Cleanup resources that won't be released when this object is garbage-collected
262          */
263         public void destroy() {
264
265                 resolver.destroy();
266                 arpEngine.destroy();
267         }
268
269         public ArtifactMapper getArtifactMapper() {
270
271                 return artifactMapper;
272         }
273
274         public Trust getTrust() {
275
276                 return trust;
277         }
278
279         private class Semaphore {
280
281                 private int value;
282
283                 public Semaphore(int value) {
284
285                         this.value = value;
286                 }
287
288                 public synchronized void enter() {
289
290                         --value;
291                         if (value < 0) {
292                                 try {
293                                         wait();
294                                 } catch (InterruptedException e) {
295                                         // squelch and continue
296                                 }
297                         }
298                 }
299
300                 public synchronized void exit() {
301
302                         ++value;
303                         notify();
304                 }
305         }
306 }