2346411f59cf6b91947725b7829c119942ad589d
[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.common.Credential;
54 import edu.internet2.middleware.shibboleth.common.NameMapper;
55 import edu.internet2.middleware.shibboleth.common.RelyingParty;
56 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
57 import edu.internet2.middleware.shibboleth.metadata.EntityDescriptor;
58 import edu.internet2.middleware.shibboleth.metadata.Metadata;
59 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
60 import edu.internet2.middleware.shibboleth.metadata.SPSSODescriptor;
61
62 /**
63  * Delivers core IdP functionality (Attribute resolution, ARP filtering, Metadata lookup, Signing, Mapping between local &
64  * SAML identifiers, etc.) to components that process protocol-specific requests.
65  * 
66  * @author Walter Hoehn
67  */
68 public class IdPProtocolSupport implements Metadata {
69
70         private static Logger log = Logger.getLogger(IdPProtocolSupport.class.getName());
71         private Logger transactionLog;
72         private IdPConfig config;
73         private ArrayList fedMetadata = new ArrayList();
74         private NameMapper nameMapper;
75         private ServiceProviderMapper spMapper;
76         private ArpEngine arpEngine;
77         private AttributeResolver resolver;
78
79         IdPProtocolSupport(IdPConfig config, Logger transactionLog, NameMapper nameMapper, ServiceProviderMapper spMapper,
80                         ArpEngine arpEngine, AttributeResolver resolver) {
81
82                 this.transactionLog = transactionLog;
83                 this.config = config;
84                 this.nameMapper = nameMapper;
85                 this.spMapper = spMapper;
86                 spMapper.setMetadata(this);
87                 this.arpEngine = arpEngine;
88                 this.resolver = resolver;
89         }
90
91         public static void validateEngineData(HttpServletRequest req) throws InvalidClientDataException {
92
93                 //TODO this should be pulled out into handlers
94
95                 if ((req.getRemoteAddr() == null) || (req.getRemoteAddr().equals(""))) { throw new InvalidClientDataException(
96                                 "Unable to obtain client address."); }
97         }
98
99         public Logger getTransactionLog() {
100
101                 return transactionLog;
102         }
103
104         public IdPConfig getIdPConfig() {
105
106                 return config;
107         }
108
109         public NameMapper getNameMapper() {
110
111                 return nameMapper;
112         }
113
114         public ServiceProviderMapper getServiceProviderMapper() {
115
116                 return spMapper;
117         }
118
119         public static void addSignatures(SAMLResponse response, RelyingParty relyingParty, EntityDescriptor provider,
120                         boolean signResponse) throws SAMLException {
121
122                 if (provider != null) {
123                         boolean signAssertions = false;
124
125                         SPSSODescriptor sp = provider.getSPSSODescriptor(org.opensaml.XML.SAML11_PROTOCOL_ENUM);
126                         if (sp == null) {
127                                 log.info("Inappropriate metadata for provider: " + provider.getId() + ".  Expected SPSSODescriptor.");
128                         }
129                         if (sp.getWantAssertionsSigned()) {
130                                 signAssertions = true;
131                         }
132
133                         if (signAssertions && relyingParty.getIdentityProvider().getSigningCredential() != null
134                                         && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
135
136                                 Iterator assertions = response.getAssertions();
137
138                                 while (assertions.hasNext()) {
139                                         SAMLAssertion assertion = (SAMLAssertion) assertions.next();
140                                         String assertionAlgorithm;
141                                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
142                                                 assertionAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
143                                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
144                                                 assertionAlgorithm = 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
150                                         assertion.sign(assertionAlgorithm, relyingParty.getIdentityProvider().getSigningCredential()
151                                                         .getPrivateKey(), Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential()
152                                                         .getX509CertificateChain()));
153                                 }
154                         }
155                 }
156
157                 // Sign the response, if appropriate
158                 if (signResponse && relyingParty.getIdentityProvider().getSigningCredential() != null
159                                 && relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey() != null) {
160
161                         String responseAlgorithm;
162                         if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.RSA) {
163                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
164                         } else if (relyingParty.getIdentityProvider().getSigningCredential().getCredentialType() == Credential.DSA) {
165                                 responseAlgorithm = XMLSignature.ALGO_ID_SIGNATURE_DSA;
166                         } else {
167                                 throw new InvalidCryptoException(SAMLException.RESPONDER,
168                                                 "The Shibboleth IdP currently only supports signing with RSA and DSA keys.");
169                         }
170
171                         response.sign(responseAlgorithm, relyingParty.getIdentityProvider().getSigningCredential().getPrivateKey(),
172                                         Arrays.asList(relyingParty.getIdentityProvider().getSigningCredential().getX509CertificateChain()));
173                 }
174         }
175
176         protected void addFederationProvider(Element element) {
177
178                 log.debug("Found Federation Provider configuration element.");
179                 if (!element.getTagName().equals("FederationProvider")) {
180                         log.error("Error while attemtping to load Federation Provider.  Malformed provider specificaion.");
181                         return;
182                 }
183
184                 try {
185                         fedMetadata.add(FederationProviderFactory.loadProvider(element));
186                 } catch (MetadataException e) {
187                         log.error("Unable to load Federation Provider.  Skipping...");
188                 }
189         }
190
191         public int providerCount() {
192
193                 return fedMetadata.size();
194         }
195
196         public EntityDescriptor lookup(String providerId) {
197
198                 Iterator iterator = fedMetadata.iterator();
199                 while (iterator.hasNext()) {
200                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(providerId);
201                         if (provider != null) { return provider; }
202                 }
203                 return null;
204         }
205
206         public EntityDescriptor lookup(Artifact artifact) {
207
208                 Iterator iterator = fedMetadata.iterator();
209                 while (iterator.hasNext()) {
210                         EntityDescriptor provider = ((Metadata) iterator.next()).lookup(artifact);
211                         if (provider != null) { return provider; }
212                 }
213                 return null;
214         }
215
216         public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource) throws AAException {
217
218                 try {
219                         URI[] potentialAttributes = arpEngine.listPossibleReleaseAttributes(principal, requester, resource);
220                         return getReleaseAttributes(principal, requester, resource, potentialAttributes);
221
222                 } catch (ArpProcessingException e) {
223                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
224                                         + e.getMessage());
225                         throw new AAException("Error retrieving data for principal.");
226                 }
227         }
228
229         public SAMLAttribute[] getReleaseAttributes(Principal principal, String requester, URL resource,
230                         URI[] attributeNames) throws AAException {
231
232                 try {
233                         AAAttributeSet attributeSet = new AAAttributeSet();
234                         for (int i = 0; i < attributeNames.length; i++) {
235                                 AAAttribute attribute = new AAAttribute(attributeNames[i].toString());
236                                 attributeSet.add(attribute);
237                         }
238
239                         return resolveAttributes(principal, requester, resource, attributeSet);
240
241                 } catch (SAMLException e) {
242                         log.error("An error occurred while creating attributes for principal (" + principal.getName() + ") :"
243                                         + e.getMessage());
244                         throw new AAException("Error retrieving data for principal.");
245
246                 } catch (ArpProcessingException e) {
247                         log.error("An error occurred while processing the ARPs for principal (" + principal.getName() + ") :"
248                                         + e.getMessage());
249                         throw new AAException("Error retrieving data for principal.");
250                 }
251         }
252
253         private SAMLAttribute[] resolveAttributes(Principal principal, String requester, URL resource,
254                         AAAttributeSet attributeSet) throws ArpProcessingException {
255
256                 resolver.resolveAttributes(principal, requester, attributeSet);
257                 arpEngine.filterAttributes(attributeSet, principal, requester, resource);
258                 return attributeSet.getAttributes();
259         }
260
261         /**
262          * Cleanup resources that won't be released when this object is garbage-collected
263          */
264         public void destroy() {
265
266                 resolver.destroy();
267                 arpEngine.destroy();
268         }
269 }