Fixed NullPointerException on incorrect or missing attribute.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / AAResponder.java
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
14  * Redistributions in binary form must reproduce the above copyright notice, 
15  * this list of conditions and the following disclaimer in the documentation 
16  * and/or other materials provided with the distribution, if any, must include 
17  * the following acknowledgment: "This product includes software developed by 
18  * the University Corporation for Advanced Internet Development 
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
20  * may appear in the software itself, if and wherever such third-party 
21  * acknowledgments normally appear.
22  * 
23  * Neither the name of Shibboleth nor the names of its contributors, nor 
24  * Internet2, nor the University Corporation for Advanced Internet Development, 
25  * Inc., nor UCAID may be used to endorse or promote products derived from this 
26  * software without specific prior written permission. For written permission, 
27  * please contact shibboleth@shibboleth.org
28  * 
29  * Products derived from this software may not be called Shibboleth, Internet2, 
30  * UCAID, or the University Corporation for Advanced Internet Development, nor 
31  * may Shibboleth appear in their name, without prior written permission of the 
32  * University Corporation for Advanced Internet Development.
33  * 
34  * 
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 package edu.internet2.middleware.shibboleth.aa;
51
52 /**
53  *  Attribute Authority & Release Policy
54  *  Main logic that decides what to release 
55  *
56  * @author     Parviz Dousti (dousti@cmu.edu)
57  * @author     Walter Hoehn (wassa@columbia.edu)
58  */
59
60 import java.net.URI;
61 import java.net.URL;
62 import java.security.Principal;
63 import java.util.ArrayList;
64 import java.util.HashSet;
65 import java.util.List;
66 import java.util.Set;
67
68 import javax.naming.CommunicationException;
69 import javax.naming.NameNotFoundException;
70 import javax.naming.NamingEnumeration;
71 import javax.naming.NamingException;
72 import javax.naming.directory.Attribute;
73 import javax.naming.directory.Attributes;
74 import javax.naming.directory.DirContext;
75 import javax.naming.directory.InitialDirContext;
76 import javax.naming.directory.SearchControls;
77 import javax.naming.directory.SearchResult;
78
79 import org.apache.log4j.Logger;
80 import org.opensaml.SAMLAttribute;
81 import org.opensaml.SAMLException;
82
83 import edu.internet2.middleware.shibboleth.aa.arp.ArpAttribute;
84 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
85 import edu.internet2.middleware.shibboleth.aa.arp.ArpProcessingException;
86 import edu.internet2.middleware.shibboleth.aa.arp.provider.ShibArpAttribute;
87
88 public class AAResponder {
89
90         protected ArpEngine arpEngine;
91         protected DirContext ctx;
92         protected String domain;
93         private static Logger log = Logger.getLogger(AAResponder.class.getName());
94
95         public AAResponder(ArpEngine arpEngine, DirContext ctx, String domain) throws AAException {
96
97                 this.arpEngine = arpEngine;
98                 this.ctx = ctx;
99                 this.domain = domain;
100         }
101
102         public SAMLAttribute[] getReleaseAttributes(
103                 Principal principal,
104                 String searchFilter,
105                 String requester,
106                 URL resource)
107                 throws AAException {
108
109                 DirContext userCtx = queryDataSource(principal, searchFilter);
110
111                 try {
112                         //optimization... find out which attributes to resolve
113                         URI[] potentialAttributes =
114                                 arpEngine.listPossibleReleaseAttributes(principal, requester, resource);
115
116                         //resolve for each attribute
117                         Set arpAttributes = new HashSet();
118
119                         for (int i = 0; i < potentialAttributes.length; i++) {
120                                 ShibArpAttribute arpAttribute = new ShibArpAttribute(potentialAttributes[i].toString());
121
122                                 Attributes attrs =
123                                         ctx.getAttributes(
124                                                 "",
125                                                 new String[] {
126                                                          arpAttribute.getName().substring(arpAttribute.getName().lastIndexOf(":") + 1)});
127                                 Attribute dAttr =
128                                         attrs.get(arpAttribute.getName().substring(arpAttribute.getName().lastIndexOf(":") + 1));
129                                 if (dAttr == null) {
130                                         continue;       
131                                 }
132                                 NamingEnumeration directoryValuesEnum = dAttr.getAll();
133                                 List directoryValues = new ArrayList();
134                                 while (directoryValuesEnum.hasMoreElements()) {
135                                         directoryValues.add(directoryValuesEnum.next());
136                                 }
137                                 arpAttribute.setValues(directoryValues.toArray());
138                                 arpAttributes.add(arpAttribute);
139                         }
140
141                         //filter and convert to SAML
142                         ArpAttribute[] filteredAttributes =
143                                 arpEngine.filterAttributes(
144                                         (ArpAttribute[]) arpAttributes.toArray(new ArpAttribute[0]),
145                                         principal,
146                                         requester,
147                                         resource);
148
149                         Set samlAttributes = new HashSet();
150                         for (int i = 0; i < filteredAttributes.length; i++) {
151                                 samlAttributes.add(toSaml(filteredAttributes[i], requester));
152                         }
153                         return (SAMLAttribute[]) samlAttributes.toArray(new SAMLAttribute[0]);
154
155                 } catch (NamingException e) {
156                         log.error(
157                                 "An error occurred while retieving data for principal ("
158                                         + principal.getName()
159                                         + ") :"
160                                         + e.getMessage());
161                         throw new AAException("Error retrieving data for principal.");
162                 } catch (ArpProcessingException e) {
163                         log.error(
164                                 "An error occurred while processing the ARPs for principal ("
165                                         + principal.getName()
166                                         + ") :"
167                                         + e.getMessage());
168                         throw new AAException("Error retrieving data for principal.");
169                 }
170
171         }
172
173         private DirContext queryDataSource(Principal principal, String searchFilter)
174                 throws AAException {
175                 try {
176                         try {
177                                 return getUserContext(principal.getName(), searchFilter);
178                         } catch (CommunicationException ce) {
179                                 synchronized (ctx) {
180                                         log.debug(ce);
181                                         log.warn(
182                                                 "Encountered a connection problem while querying for attributes.  Re-initializing JNDI context and retrying...");
183                                         ctx = new InitialDirContext(ctx.getEnvironment());
184                                 }
185                                 return getUserContext(principal.getName(), searchFilter);
186                         }
187                 } catch (NamingException e) {
188                         log.error(
189                                 "An error occurred while retieving data for principal ("
190                                         + principal.getName()
191                                         + ") :"
192                                         + e.getMessage());
193                         throw new AAException("Error retrieving data for principal.");
194                 }
195         }
196
197         private DirContext getUserContext(String userName, String searchFilter)
198                 throws CommunicationException, NamingException, AAException {
199
200                 DirContext userCtx = null;
201                 if (searchFilter == null) {
202                         searchFilter = "";
203                 }
204                 int indx = searchFilter.indexOf("%s");
205                 if (indx < 0) {
206                         try {
207                                 userCtx = (DirContext) ctx.lookup(searchFilter + userName);
208                         } catch (NameNotFoundException nnfe) {
209                                 log.error(
210                                         "Could not locate a user ("
211                                                 + userName
212                                                 + ") as a result of searching with ("
213                                                 + searchFilter
214                                                 + ").");
215                                 throw new AAException("No data available for this principal.");
216                         }
217                 } else {
218                         /* This is a search filter. Search after replacing %s with uid*/
219                         StringBuffer tmp = new StringBuffer(searchFilter);
220                         tmp.delete(indx, indx + 2);
221                         tmp.insert(indx, userName);
222                         searchFilter = tmp.toString();
223                         SearchControls ctls = new SearchControls();
224                         ctls.setReturningObjFlag(true);
225                         NamingEnumeration en = ctx.search("", searchFilter, ctls);
226                         if (!en.hasMore()) {
227                                 log.error(
228                                         "Could not locate a user ("
229                                                 + userName
230                                                 + ") as a result of searching with ("
231                                                 + searchFilter
232                                                 + ").");
233                                 throw new AAException("No data available for this principal.");
234                         }
235                         userCtx = (DirContext) ((SearchResult) en.next()).getObject();
236                         if (en.hasMore()) {
237                                 log.error(
238                                         "Located multiple ("
239                                                 + userName
240                                                 + ") users as a result of searching with ("
241                                                 + searchFilter
242                                                 + ").");
243                                 throw new AAException("Cannot disambiguate data for this principal.");
244                         }
245                 }
246                 return userCtx;
247         }
248
249         private SAMLAttribute toSaml(ArpAttribute attribute, String recipient)
250                 throws NamingException, AAException {
251
252                 if (attribute == null) {
253                         return null;
254                 }
255
256                 log.debug("Converting Attribute (" + attribute.getName() + ") to SAML.");
257
258                 try {
259                         Class attrClass =
260                                 Class.forName(
261                                         "edu.internet2.middleware.shibboleth.aaLocal.attributes."
262                                                 + attribute.getName().substring(
263                                                         attribute.getName().lastIndexOf(":") + 1));
264                         log.debug("Loaded the class for " + attrClass);
265                         ShibAttribute sa = (ShibAttribute) attrClass.newInstance();
266                         return sa.toSamlAttribute(this.domain, attribute.getValues(), recipient);
267
268                 } catch (SAMLException e) {
269                         log.error(
270                                 "Error converting attribute to SAML ("
271                                         + attribute.getName()
272                                         + ") :"
273                                         + e.getMessage());
274                         return null;
275                 } catch (Exception e) {
276                         log.error("Failed to load the class for attribute (" + attribute.getName() + ") :" + e);
277                         return null;
278                 }
279
280         }
281 }