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