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