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