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