97e373addeed0175a2a8d33ca16d6a6012aefabe
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPProtocolSupport.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp;
18
19 import java.net.URI;
20 import java.net.URL;
21 import java.security.Principal;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Iterator;
25
26 import org.apache.log4j.Logger;
27 import org.apache.xml.security.signature.XMLSignature;
28 import org.opensaml.InvalidCryptoException;
29 import org.opensaml.SAMLAssertion;
30 import org.opensaml.SAMLAttribute;
31 import org.opensaml.SAMLException;
32 import org.opensaml.SAMLResponse;
33 import org.opensaml.artifact.Artifact;
34 import org.w3c.dom.Element;
35
36 import edu.internet2.middleware.shibboleth.aa.AAAttribute;
37 import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
38 import edu.internet2.middleware.shibboleth.aa.AAException;
39 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
40 import edu.internet2.middleware.shibboleth.aa.arp.ArpProcessingException;
41 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
42 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
43 import edu.internet2.middleware.shibboleth.common.Credential;
44 import edu.internet2.middleware.shibboleth.common.NameMapper;
45 import edu.internet2.middleware.shibboleth.common.RelyingParty;
46 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
47 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
48 import edu.internet2.middleware.shibboleth.common.provider.ShibbolethTrust;
49 import edu.internet2.middleware.shibboleth.common.Trust;
50 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
51 import edu.internet2.middleware.shibboleth.metadata.Metadata;
52 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
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 Metadata {
61
62         private static Logger log = Logger.getLogger(IdPProtocolSupport.class.getName());
63         private Logger transactionLog;
64         private IdPConfig config;
65         private ArrayList metadata = new ArrayList();
66         private NameMapper nameMapper;
67         private ServiceProviderMapper spMapper;
68         private ArpEngine arpEngine;
69         private AttributeResolver resolver;
70         private ArtifactMapper artifactMapper;
71         private Semaphore throttle;
72         private Trust trust = new ShibbolethTrust();
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                         metadata.add(MetadataProviderFactory.loadProvider(element));
176                 } catch (MetadataException e) {
177                         log.error("Unable to load Metadata Provider.  Skipping...");
178                 }
179         }
180
181         public int providerCount() {
182
183                 return metadata.size();
184         }
185
186         public EntityDescriptor lookup(String providerId) {
187
188                 Iterator iterator = metadata.iterator();
189                 while (iterator.hasNext()) {
190                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(providerId);
191                         if (provider != null) { return provider; }
192                 }
193                 return null;
194         }
195
196         public EntityDescriptor lookup(Artifact artifact) {
197
198                 Iterator iterator = metadata.iterator();
199                 while (iterator.hasNext()) {
200                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(artifact);
201                         if (provider != null) { return provider; }
202                 }
203                 return null;
204         }
205
206         public SAMLAttribute[] getReleaseAttributes(Principal principal, RelyingParty relyingParty, String requester,
207                         URL resource) throws AAException {
208
209                 try {
210                         URI[] potentialAttributes = arpEngine.listPossibleReleaseAttributes(principal, requester, resource);
211                         return getReleaseAttributes(principal, relyingParty, requester, resource, potentialAttributes);
212
213                 } catch (ArpProcessingException e) {
214                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
215                                         + e.getMessage());
216                         throw new AAException("Error retrieving data for principal.");
217                 }
218         }
219
220         public SAMLAttribute[] getReleaseAttributes(Principal principal, RelyingParty relyingParty, String requester,
221                         URL resource, URI[] attributeNames) throws AAException {
222
223                 try {
224                         AAAttributeSet attributeSet = new AAAttributeSet();
225                         for (int i = 0; i < attributeNames.length; i++) {
226
227                                 AAAttribute attribute = null;
228                                 if (relyingParty.wantsSchemaHack()) {
229                                         attribute = new AAAttribute(attributeNames[i].toString(), true);
230                                 } else {
231                                         attribute = new AAAttribute(attributeNames[i].toString(), false);
232                                 }
233
234                                 attributeSet.add(attribute);
235                         }
236
237                         return resolveAttributes(principal, requester, relyingParty.getIdentityProvider().getProviderId(),
238                                         resource, attributeSet);
239
240                 } catch (SAMLException e) {
241                         log.error("An error occurred while creating attributes for principal (" + principal.getName() + ") :"
242                                         + e.getMessage());
243                         throw new AAException("Error retrieving data for principal.");
244
245                 } catch (ArpProcessingException e) {
246                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
247                                         + e.getMessage());
248                         throw new AAException("Error retrieving data for principal.");
249                 }
250         }
251
252         private SAMLAttribute[] resolveAttributes(Principal principal, String requester, String responder, URL resource,
253                         AAAttributeSet attributeSet) throws ArpProcessingException {
254
255                 resolver.resolveAttributes(principal, requester, responder, attributeSet);
256                 arpEngine.filterAttributes(attributeSet, principal, requester, resource);
257                 return attributeSet.getAttributes();
258         }
259
260         /**
261          * Cleanup resources that won't be released when this object is garbage-collected
262          */
263         public void destroy() {
264
265                 resolver.destroy();
266                 arpEngine.destroy();
267         }
268
269         public ArtifactMapper getArtifactMapper() {
270
271                 return artifactMapper;
272         }
273
274         public Trust getTrust() {
275
276                 return trust;
277         }
278
279         private class Semaphore {
280
281                 private int value;
282
283                 public Semaphore(int value) {
284
285                         this.value = value;
286                 }
287
288                 public synchronized void enter() {
289
290                         --value;
291                         if (value < 0) {
292                                 try {
293                                         wait();
294                                 } catch (InterruptedException e) {
295                                         // squelch and continue
296                                 }
297                         }
298                 }
299
300                 public synchronized void exit() {
301
302                         ++value;
303                         notify();
304                 }
305         }
306 }