SDSS WAYF patch for multi-federation support
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPProtocolSupport.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.] Licensed under the Apache License,
3  * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy
4  * of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in
5  * writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
6  * OF ANY KIND, either express or implied. See the License for the specific language governing permissions and
7  * limitations under the License.
8  */
9
10 package edu.internet2.middleware.shibboleth.idp;
11
12 import java.net.URI;
13 import java.net.URL;
14 import java.security.Principal;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.Iterator;
18
19 import org.apache.log4j.Logger;
20 import org.apache.xml.security.signature.XMLSignature;
21 import org.opensaml.InvalidCryptoException;
22 import org.opensaml.SAMLAssertion;
23 import org.opensaml.SAMLAttribute;
24 import org.opensaml.SAMLException;
25 import org.opensaml.SAMLResponse;
26 import org.opensaml.artifact.Artifact;
27 import org.w3c.dom.Element;
28
29 import edu.internet2.middleware.shibboleth.aa.AAAttribute;
30 import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
31 import edu.internet2.middleware.shibboleth.aa.AAException;
32 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
33 import edu.internet2.middleware.shibboleth.aa.arp.ArpProcessingException;
34 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
35 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
36 import edu.internet2.middleware.shibboleth.common.Credential;
37 import edu.internet2.middleware.shibboleth.common.NameMapper;
38 import edu.internet2.middleware.shibboleth.common.RelyingParty;
39 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
40 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
41 import edu.internet2.middleware.shibboleth.common.Trust;
42 import edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrust;
43 import edu.internet2.middleware.shibboleth.metadata.EntitiesDescriptor;
44 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
45 import edu.internet2.middleware.shibboleth.metadata.Metadata;
46 import edu.internet2.middleware.shibboleth.metadata.MetadataProviderFactory;
47 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
48
49 /**
50  * Delivers core IdP functionality (Attribute resolution, ARP filtering, Metadata lookup, Signing, Mapping between local &
51  * SAML identifiers, etc.) to components that process protocol-specific requests.
52  * 
53  * @author Walter Hoehn
54  */
55 public class IdPProtocolSupport implements Metadata {
56
57         private static Logger log = Logger.getLogger(IdPProtocolSupport.class.getName());
58         private Logger transactionLog;
59         private IdPConfig config;
60         private ArrayList metadata = new ArrayList();
61         private NameMapper nameMapper;
62         private ServiceProviderMapper spMapper;
63         private ArpEngine arpEngine;
64         private AttributeResolver resolver;
65         private ArtifactMapper artifactMapper;
66         private Semaphore throttle;
67         private Trust trust = new ShibbolethTrust();
68
69         IdPProtocolSupport(IdPConfig config, Logger transactionLog, NameMapper nameMapper, ServiceProviderMapper spMapper,
70                         ArpEngine arpEngine, AttributeResolver resolver, ArtifactMapper artifactMapper)
71                         throws ShibbolethConfigurationException {
72
73                 this.transactionLog = transactionLog;
74                 this.config = config;
75                 this.nameMapper = nameMapper;
76                 this.spMapper = spMapper;
77                 spMapper.setMetadata(this);
78                 this.arpEngine = arpEngine;
79                 this.resolver = resolver;
80                 this.artifactMapper = artifactMapper;
81
82                 // Load a semaphore that throttles how many requests the IdP will handle at once
83                 throttle = new Semaphore(config.getMaxThreads());
84         }
85
86         public Logger getTransactionLog() {
87
88                 return transactionLog;
89         }
90
91         public IdPConfig getIdPConfig() {
92
93                 return config;
94         }
95
96         public NameMapper getNameMapper() {
97
98                 return nameMapper;
99         }
100
101         public ServiceProviderMapper getServiceProviderMapper() {
102
103                 return spMapper;
104         }
105
106         public void signAssertions(SAMLAssertion[] assertions, RelyingParty relyingParty) throws InvalidCryptoException,
107                         SAMLException {
108
109                 if (relyingParty.getIdentityProvider().getSigningCredential() == null
110                                 || relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() == null) { throw new InvalidCryptoException(
111                                 SAMLException.RESPONDER, "Invalid signing credential."); }
112
113                 for (int i = 0; i < assertions.length; i++) {
114                         String assertionAlgorithm;
115                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
116                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
117                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
118                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
119                         } else {
120                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
121                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
122                         }
123
124                         try {
125                                 throttle.enter();
126                                 assertions[i].sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
127                                                 .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
128                                                 .getX509CertificateChain()));
129                         } finally {
130                                 throttle.exit();
131                         }
132                 }
133         }
134
135         public void signResponse(SAMLResponse response, RelyingParty relyingParty) throws SAMLException {
136
137                 // Make sure we have an appropriate credential
138                 if (relyingParty.getIdentityProvider().getSigningCredential() == null
139                                 || relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() == null) { throw new InvalidCryptoException(
140                                 SAMLException.RESPONDER, "Invalid signing credential."); }
141
142                 // Sign the response
143                 String responseAlgorithm;
144                 if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
145                         responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
146                 } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
147                         responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
148                 } else {
149                         throw new InvalidCryptoException(SAMLException.RESPONDER,
150                                         "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
151                 }
152                 try {
153                         throttle.enter();
154                         response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
155                                         Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
156                 } finally {
157                         throttle.exit();
158                 }
159         }
160
161         protected void addMetadataProvider(Element element) {
162
163                 log.debug("Found Metadata Provider configuration element.");
164                 if (!element.getTagName().equals("MetadataProvider")) {
165                         log.error("Error while attemtping to load Metadata Provider.  Malformed provider specificaion.");
166                         return;
167                 }
168
169                 try {
170                         metadata.add(MetadataProviderFactory.loadProvider(element));
171                 } catch (MetadataException e) {
172                         log.error("Unable to load Metadata Provider.  Skipping...");
173                 }
174         }
175
176         public int providerCount() {
177
178                 return metadata.size();
179         }
180
181         public EntityDescriptor lookup(String providerId, boolean strict) {
182
183                 Iterator iterator = metadata.iterator();
184                 while (iterator.hasNext()) {
185                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(providerId);
186                         if (provider != null) { return provider; }
187                 }
188                 return null;
189         }
190
191         public EntityDescriptor lookup(Artifact artifact, boolean strict) {
192
193                 Iterator iterator = metadata.iterator();
194                 while (iterator.hasNext()) {
195                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(artifact);
196                         if (provider != null) { return provider; }
197                 }
198                 return null;
199         }
200
201         public EntityDescriptor lookup(String id) {
202
203                 return lookup(id, true);
204         }
205
206         public EntityDescriptor lookup(Artifact artifact) {
207
208                 return lookup(artifact, true);
209         }
210
211         public EntityDescriptor getRootEntity() {
212
213                 return null;
214         }
215
216         public EntitiesDescriptor getRootEntities() {
217
218                 return null;
219         }
220
221         public SAMLAttribute[] getReleaseAttributes(Principal principal, RelyingParty relyingParty, String requester,
222                         URL resource) throws AAException {
223
224                 try {
225                         URI[] potentialAttributes = arpEngine.listPossibleReleaseAttributes(principal, requester, resource);
226                         return getReleaseAttributes(principal, relyingParty, requester, resource, potentialAttributes);
227
228                 } catch (ArpProcessingException e) {
229                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
230                                         + e.getMessage());
231                         throw new AAException("Error retrieving data for principal.");
232                 }
233         }
234
235         public SAMLAttribute[] getReleaseAttributes(Principal principal, RelyingParty relyingParty, String requester,
236                         URL resource, URI[] attributeNames) throws AAException {
237
238                 try {
239                         AAAttributeSet attributeSet = new AAAttributeSet();
240                         for (int i = 0; i < attributeNames.length; i++) {
241
242                                 AAAttribute attribute = null;
243                                 if (relyingParty.wantsSchemaHack()) {
244                                         attribute = new AAAttribute(attributeNames[i].toString(), true);
245                                 } else {
246                                         attribute = new AAAttribute(attributeNames[i].toString(), false);
247                                 }
248
249                                 attributeSet.add(attribute);
250                         }
251
252                         return resolveAttributes(principal, requester, relyingParty.getIdentityProvider().getProviderId(),
253                                         resource, attributeSet);
254
255                 } catch (SAMLException e) {
256                         log.error("An error occurred while creating attributes for principal (" + principal.getName() + ") :"
257                                         + e.getMessage());
258                         throw new AAException("Error retrieving data for principal.");
259
260                 } catch (ArpProcessingException e) {
261                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
262                                         + e.getMessage());
263                         throw new AAException("Error retrieving data for principal.");
264                 }
265         }
266
267         public SAMLAttribute[] resolveAttributes(Principal principal, String requester, String responder, URL resource,
268                         AAAttributeSet attributeSet) throws ArpProcessingException {
269
270                 resolver.resolveAttributes(principal, requester, responder, attributeSet);
271                 arpEngine.filterAttributes(attributeSet, principal, requester, resource);
272                 return attributeSet.getAttributes();
273         }
274
275         public SAMLAttribute[] resolveAttributesNoPolicies(Principal principal, String requester, String responder,
276                         AAAttributeSet attributeSet) {
277
278                 resolver.resolveAttributes(principal, requester, responder, attributeSet);
279                 return attributeSet.getAttributes();
280         }
281
282         /**
283          * Cleanup resources that won't be released when this object is garbage-collected
284          */
285         public void destroy() {
286
287                 resolver.destroy();
288                 arpEngine.destroy();
289         }
290
291         public ArtifactMapper getArtifactMapper() {
292
293                 return artifactMapper;
294         }
295
296         public Trust getTrust() {
297
298                 return trust;
299         }
300
301         private class Semaphore {
302
303                 private int value;
304
305                 public Semaphore(int value) {
306
307                         this.value = value;
308                 }
309
310                 public synchronized void enter() {
311
312                         --value;
313                         if (value < 0) {
314                                 try {
315                                         wait();
316                                 } catch (InterruptedException e) {
317                                         // squelch and continue
318                                 }
319                         }
320                 }
321
322                 public synchronized void exit() {
323
324                         ++value;
325                         notify();
326                 }
327         }
328 }