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