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