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