Fleshed out the artifact code.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPProtocolSupport.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4  * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5  * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
6  * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
7  * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
8  * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu> Internet2 Project.
9  * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10  * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
11  * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12  * products derived from this software without specific prior written permission. For written permission, please contact
13  * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
14  * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
15  * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
16  * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
18  * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
19  * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 package edu.internet2.middleware.shibboleth.idp;
27
28 import java.net.URI;
29 import java.net.URL;
30 import java.security.Principal;
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Iterator;
34
35 import javax.servlet.http.HttpServletRequest;
36
37 import org.apache.log4j.Logger;
38 import org.apache.xml.security.signature.XMLSignature;
39 import org.opensaml.InvalidCryptoException;
40 import org.opensaml.SAMLAssertion;
41 import org.opensaml.SAMLAttribute;
42 import org.opensaml.SAMLException;
43 import org.opensaml.SAMLResponse;
44 import org.opensaml.artifact.Artifact;
45 import org.w3c.dom.Element;
46
47 import edu.internet2.middleware.shibboleth.aa.AAAttribute;
48 import edu.internet2.middleware.shibboleth.aa.AAAttributeSet;
49 import edu.internet2.middleware.shibboleth.aa.AAException;
50 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
51 import edu.internet2.middleware.shibboleth.aa.arp.ArpProcessingException;
52 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
53 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
54 import edu.internet2.middleware.shibboleth.artifact.provider.MemoryArtifactMapper;
55 import edu.internet2.middleware.shibboleth.common.Credential;
56 import edu.internet2.middleware.shibboleth.common.NameMapper;
57 import edu.internet2.middleware.shibboleth.common.RelyingParty;
58 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
59 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
60 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
61 import edu.internet2.middleware.shibboleth.metadata.Metadata;
62 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
63 import edu.internet2.middleware.shibboleth.metadata.SPSSODescriptor;
64
65 /**
66  * Delivers core IdP functionality (Attribute resolution, ARP filtering, Metadata lookup, Signing, Mapping between local &
67  * SAML identifiers, etc.) to components that process protocol-specific requests.
68  * 
69  * @author Walter Hoehn
70  */
71 public class IdPProtocolSupport implements Metadata {
72
73         private static Logger log = Logger.getLogger(IdPProtocolSupport.class.getName());
74         private Logger transactionLog;
75         private IdPConfig config;
76         private ArrayList fedMetadata = new ArrayList();
77         private NameMapper nameMapper;
78         private ServiceProviderMapper spMapper;
79         private ArpEngine arpEngine;
80         private AttributeResolver resolver;
81         private ArtifactMapper artifactMapper;
82
83         IdPProtocolSupport(IdPConfig config, Logger transactionLog, NameMapper nameMapper, ServiceProviderMapper spMapper,
84                         ArpEngine arpEngine, AttributeResolver resolver) throws ShibbolethConfigurationException {
85
86                 this.transactionLog = transactionLog;
87                 this.config = config;
88                 this.nameMapper = nameMapper;
89                 this.spMapper = spMapper;
90                 spMapper.setMetadata(this);
91                 this.arpEngine = arpEngine;
92                 this.resolver = resolver;
93                 // TODO make this pluggable... and clean up memory impl
94                 artifactMapper = new MemoryArtifactMapper();
95         }
96
97         public static void validateEngineData(HttpServletRequest req) throws InvalidClientDataException {
98
99                 // TODO this should be pulled out into handlers
100
101                 if ((req.getRemoteAddr() == null) || (req.getRemoteAddr().equals(""))) { throw new InvalidClientDataException(
102                                 "Unable to obtain client address."); }
103         }
104
105         public Logger getTransactionLog() {
106
107                 return transactionLog;
108         }
109
110         public IdPConfig getIdPConfig() {
111
112                 return config;
113         }
114
115         public NameMapper getNameMapper() {
116
117                 return nameMapper;
118         }
119
120         public ServiceProviderMapper getServiceProviderMapper() {
121
122                 return spMapper;
123         }
124
125         public static void addSignatures(SAMLResponse response, RelyingParty relyingParty, EntityDescriptor provider,
126                         boolean signResponse) throws SAMLException {
127
128                 if (provider != null) {
129                         boolean signAssertions = false;
130
131                         SPSSODescriptor sp = provider.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
132                         if (sp == null) {
133                                 log.info("Inappropriate metadata for provider: " + provider.getId() + ".  Expected SPSSODescriptor.");
134                         }
135                         if (sp.getWantAssertionsSigned()) {
136                                 signAssertions = true;
137                         }
138
139                         if (signAssertions && relyingParty.getIdentityProvider().getSigningCredential() != null
140                                         && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
141
142                                 Iterator assertions = response.getAssertions();
143
144                                 while (assertions.hasNext()) {
145                                         SAMLAssertion assertion = (SAMLAssertion) assertions.next();
146                                         String assertionAlgorithm;
147                                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
148                                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
149                                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
150                                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
151                                         } else {
152                                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
153                                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
154                                         }
155
156                                         assertion.sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
157                                                         .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
158                                                         .getX509CertificateChain()));
159                                 }
160                         }
161                 }
162
163                 // Sign the response, if appropriate
164                 if (signResponse && relyingParty.getIdentityProvider().getSigningCredential() != null
165                                 && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
166
167                         String responseAlgorithm;
168                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
169                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
170                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
171                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
172                         } else {
173                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
174                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
175                         }
176
177                         response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
178                                         Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
179                 }
180         }
181
182         protected void addFederationProvider(Element element) {
183
184                 log.debug("Found Federation Provider configuration element.");
185                 if (!element.getTagName().equals("FederationProvider")) {
186                         log.error("Error while attemtping to load Federation Provider.  Malformed provider specificaion.");
187                         return;
188                 }
189
190                 try {
191                         fedMetadata.add(FederationProviderFactory.loadProvider(element));
192                 } catch (MetadataException e) {
193                         log.error("Unable to load Federation Provider.  Skipping...");
194                 }
195         }
196
197         public int providerCount() {
198
199                 return fedMetadata.size();
200         }
201
202         public EntityDescriptor lookup(String providerId) {
203
204                 Iterator iterator = fedMetadata.iterator();
205                 while (iterator.hasNext()) {
206                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(providerId);
207                         if (provider != null) { return provider; }
208                 }
209                 return null;
210         }
211
212         public EntityDescriptor lookup(Artifact artifact) {
213
214                 Iterator iterator = fedMetadata.iterator();
215                 while (iterator.hasNext()) {
216                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(artifact);
217                         if (provider != null) { return provider; }
218                 }
219                 return null;
220         }
221
222         public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource) throws AAException {
223
224                 try {
225                         URI[] potentialAttributes = arpEngine.listPossibleReleaseAttributes(principal, requester, resource);
226                         return getReleaseAttributes(principal, requester, resource, potentialAttributes);
227
228                 } catch (ArpProcessingException e) {
229                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
230                                         + e.getMessage());
231                         throw new AAException("Error retrieving data for principal.");
232                 }
233         }
234
235         public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource,
236                         URI[] attributeNames) throws AAException {
237
238                 try {
239                         AAAttributeSet attributeSet = new AAAttributeSet();
240                         for (int i = 0; i < attributeNames.length; i++) {
241                                 AAAttribute attribute = new AAAttribute(attributeNames[i].toString());
242                                 attributeSet.add(attribute);
243                         }
244
245                         return resolveAttributes(principal, requester, resource, attributeSet);
246
247                 } catch (SAMLException e) {
248                         log.error("An error occurred while creating attributes for principal (" + principal.getName() + ") :"
249                                         + e.getMessage());
250                         throw new AAException("Error retrieving data for principal.");
251
252                 } catch (ArpProcessingException e) {
253                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
254                                         + e.getMessage());
255                         throw new AAException("Error retrieving data for principal.");
256                 }
257         }
258
259         private SAMLAttribute[] resolveAttributes(Principal principal, String requester, URL resource,
260                         AAAttributeSet attributeSet) throws ArpProcessingException {
261
262                 resolver.resolveAttributes(principal, requester, attributeSet);
263                 arpEngine.filterAttributes(attributeSet, principal, requester, resource);
264                 return attributeSet.getAttributes();
265         }
266
267         /**
268          * Cleanup resources that won't be released when this object is garbage-collected
269          */
270         public void destroy() {
271
272                 resolver.destroy();
273                 arpEngine.destroy();
274         }
275
276         public ArtifactMapper getArtifactMapper() {
277
278                 return artifactMapper;
279         }
280 }