Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
JndiLdapContextFactory |
|
| 1.96;1.96 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one | |
3 | * or more contributor license agreements. See the NOTICE file | |
4 | * distributed with this work for additional information | |
5 | * regarding copyright ownership. The ASF licenses this file | |
6 | * to you under the Apache License, Version 2.0 (the | |
7 | * "License"); you may not use this file except in compliance | |
8 | * with the License. You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, | |
13 | * software distributed under the License is distributed on an | |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | * KIND, either express or implied. See the License for the | |
16 | * specific language governing permissions and limitations | |
17 | * under the License. | |
18 | */ | |
19 | package org.apache.shiro.realm.ldap; | |
20 | ||
21 | import org.apache.shiro.util.StringUtils; | |
22 | import org.slf4j.Logger; | |
23 | import org.slf4j.LoggerFactory; | |
24 | ||
25 | import javax.naming.AuthenticationException; | |
26 | import javax.naming.Context; | |
27 | import javax.naming.NamingException; | |
28 | import javax.naming.ldap.InitialLdapContext; | |
29 | import javax.naming.ldap.LdapContext; | |
30 | import java.util.HashMap; | |
31 | import java.util.Hashtable; | |
32 | import java.util.Map; | |
33 | ||
34 | /** | |
35 | * {@link LdapContextFactory} implementation using the default Sun/Oracle JNDI Ldap API, utilizing JNDI | |
36 | * environment properties and an {@link javax.naming.InitialContext}. | |
37 | * <h2>Configuration</h2> | |
38 | * This class basically wraps a default template JNDI environment properties Map. This properties map is the base | |
39 | * configuration template used to acquire JNDI {@link LdapContext} connections at runtime. The | |
40 | * {@link #getLdapContext(Object, Object)} method implementation merges this default template with other properties | |
41 | * accessible at runtime only (for example per-method principals and credentials). The constructed runtime map is the | |
42 | * one used to acquire the {@link LdapContext}. | |
43 | * <p/> | |
44 | * The template can be configured directly via the {@link #getEnvironment()}/{@link #setEnvironment(java.util.Map)} | |
45 | * properties directly if necessary, but it is usually more convenient to use the supporting wrapper get/set methods | |
46 | * for various environment properties. These wrapper methods interact with the environment | |
47 | * template on your behalf, leaving your configuration cleaner and easier to understand. | |
48 | * <p/> | |
49 | * For example, consider the following two identical configurations: | |
50 | * <pre> | |
51 | * [main] | |
52 | * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm | |
53 | * ldapRealm.contextFactory.url = ldap://localhost:389 | |
54 | * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5 | |
55 | * </pre> | |
56 | * and | |
57 | * <pre> | |
58 | * [main] | |
59 | * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm | |
60 | * ldapRealm.contextFactory.environment[java.naming.provider.url] = ldap://localhost:389 | |
61 | * ldapRealm.contextFactory.environment[java.naming.security.authentication] = DIGEST-MD5 | |
62 | * </pre> | |
63 | * As you can see, the 2nd configuration block is a little more difficult to read and also requires knowledge | |
64 | * of the underlying JNDI Context property keys. The first is easier to read and understand. | |
65 | * <p/> | |
66 | * Note that occasionally it will be necessary to use the latter configuration style to set environment properties | |
67 | * where no corresponding wrapper method exists. In this case, the hybrid approach is still a little easier to read. | |
68 | * For example: | |
69 | * <pre> | |
70 | * [main] | |
71 | * ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm | |
72 | * ldapRealm.contextFactory.url = ldap://localhost:389 | |
73 | * ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5 | |
74 | * ldapRealm.contextFactory.environment[some.other.obscure.jndi.key] = some value | |
75 | * </pre> | |
76 | * | |
77 | * @since 1.1 | |
78 | */ | |
79 | public class JndiLdapContextFactory implements LdapContextFactory { | |
80 | ||
81 | /*------------------------------------------- | |
82 | | C O N S T A N T S | | |
83 | ===========================================*/ | |
84 | /** | |
85 | * The Sun LDAP property used to enable connection pooling. This is used in the default implementation | |
86 | * to enable LDAP connection pooling. | |
87 | */ | |
88 | protected static final String SUN_CONNECTION_POOLING_PROPERTY = "com.sun.jndi.ldap.connect.pool"; | |
89 | protected static final String DEFAULT_CONTEXT_FACTORY_CLASS_NAME = "com.sun.jndi.ldap.LdapCtxFactory"; | |
90 | protected static final String SIMPLE_AUTHENTICATION_MECHANISM_NAME = "simple"; | |
91 | protected static final String DEFAULT_REFERRAL = "follow"; | |
92 | ||
93 | 1 | private static final Logger log = LoggerFactory.getLogger(JndiLdapContextFactory.class); |
94 | ||
95 | /*------------------------------------------- | |
96 | | I N S T A N C E V A R I A B L E S | | |
97 | ============================================*/ | |
98 | private Map<String, Object> environment; | |
99 | private boolean poolingEnabled; | |
100 | private String systemPassword; | |
101 | private String systemUsername; | |
102 | ||
103 | /*------------------------------------------- | |
104 | | C O N S T R U C T O R S | | |
105 | ===========================================*/ | |
106 | ||
107 | /** | |
108 | * Default no-argument constructor that initializes the backing {@link #getEnvironment() environment template} with | |
109 | * the {@link #setContextFactoryClassName(String) contextFactoryClassName} equal to | |
110 | * {@code com.sun.jndi.ldap.LdapCtxFactory} (the Sun/Oracle default) and the default | |
111 | * {@link #setReferral(String) referral} behavior to {@code follow}. | |
112 | */ | |
113 | 33 | public JndiLdapContextFactory() { |
114 | 33 | this.environment = new HashMap<String, Object>(); |
115 | 33 | setContextFactoryClassName(DEFAULT_CONTEXT_FACTORY_CLASS_NAME); |
116 | 33 | setReferral(DEFAULT_REFERRAL); |
117 | 33 | poolingEnabled = true; |
118 | 33 | } |
119 | ||
120 | /*------------------------------------------- | |
121 | | A C C E S S O R S / M O D I F I E R S | | |
122 | ===========================================*/ | |
123 | ||
124 | /** | |
125 | * Sets the type of LDAP authentication mechanism to use when connecting to the LDAP server. | |
126 | * This is a wrapper method for setting the JNDI {@link #getEnvironment() environment template}'s | |
127 | * {@link Context#SECURITY_AUTHENTICATION} property. | |
128 | * <p/> | |
129 | * "none" (i.e. anonymous) and "simple" authentications are supported automatically and don't need to be configured | |
130 | * via this property. However, if you require a different mechanism, such as a SASL or External mechanism, you | |
131 | * must configure that explicitly via this property. See the | |
132 | * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">JNDI LDAP | |
133 | * Authentication Mechanisms</a> for more information. | |
134 | * | |
135 | * @param authenticationMechanism the type of LDAP authentication to perform. | |
136 | * @see <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html"> | |
137 | * http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html</a> | |
138 | */ | |
139 | public void setAuthenticationMechanism(String authenticationMechanism) { | |
140 | 3 | setEnvironmentProperty(Context.SECURITY_AUTHENTICATION, authenticationMechanism); |
141 | 3 | } |
142 | ||
143 | /** | |
144 | * Returns the type of LDAP authentication mechanism to use when connecting to the LDAP server. | |
145 | * This is a wrapper method for getting the JNDI {@link #getEnvironment() environment template}'s | |
146 | * {@link Context#SECURITY_AUTHENTICATION} property. | |
147 | * <p/> | |
148 | * If this property remains un-configured (i.e. {@code null} indicating the | |
149 | * {@link #setAuthenticationMechanism(String)} method wasn't used), this indicates that the default JNDI | |
150 | * "none" (anonymous) and "simple" authentications are supported automatically. Any non-null value returned | |
151 | * represents an explicitly configured mechanism (e.g. a SASL or external mechanism). See the | |
152 | * <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html">JNDI LDAP | |
153 | * Authentication Mechanisms</a> for more information. | |
154 | * | |
155 | * @return the type of LDAP authentication mechanism to use when connecting to the LDAP server. | |
156 | * @see <a href="http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html"> | |
157 | * http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html</a> | |
158 | */ | |
159 | public String getAuthenticationMechanism() { | |
160 | 11 | return (String) getEnvironmentProperty(Context.SECURITY_AUTHENTICATION); |
161 | } | |
162 | ||
163 | /** | |
164 | * The name of the ContextFactory class to use. This defaults to the SUN LDAP JNDI implementation | |
165 | * but can be overridden to use custom LDAP factories. | |
166 | * <p/> | |
167 | * This is a wrapper method for setting the JNDI environment's {@link Context#INITIAL_CONTEXT_FACTORY} property. | |
168 | * | |
169 | * @param contextFactoryClassName the context factory that should be used. | |
170 | */ | |
171 | public void setContextFactoryClassName(String contextFactoryClassName) { | |
172 | 33 | setEnvironmentProperty(Context.INITIAL_CONTEXT_FACTORY, contextFactoryClassName); |
173 | 33 | } |
174 | ||
175 | /** | |
176 | * Sets the name of the ContextFactory class to use. This defaults to the SUN LDAP JNDI implementation | |
177 | * but can be overridden to use custom LDAP factories. | |
178 | * <p/> | |
179 | * This is a wrapper method for getting the JNDI environment's {@link Context#INITIAL_CONTEXT_FACTORY} property. | |
180 | * | |
181 | * @return the name of the ContextFactory class to use. | |
182 | */ | |
183 | public String getContextFactoryClassName() { | |
184 | 1 | return (String) getEnvironmentProperty(Context.INITIAL_CONTEXT_FACTORY); |
185 | } | |
186 | ||
187 | /** | |
188 | * Returns the base JNDI environment template to use when acquiring an LDAP connection (an {@link LdapContext}). | |
189 | * This property is the base configuration template to use for all connections. This template is then | |
190 | * merged with appropriate runtime values as necessary in the | |
191 | * {@link #getLdapContext(Object, Object)} implementation. The merged environment instance is what is used to | |
192 | * acquire the {@link LdapContext} at runtime. | |
193 | * <p/> | |
194 | * Most other get/set methods in this class act as thin proxy wrappers that interact with this property. The | |
195 | * benefit of using them is you have an easier-to-use configuration mechanism compared to setting map properties | |
196 | * based on JNDI context keys. | |
197 | * | |
198 | * @return the base JNDI environment template to use when acquiring an LDAP connection (an {@link LdapContext}) | |
199 | */ | |
200 | public Map getEnvironment() { | |
201 | 7 | return this.environment; |
202 | } | |
203 | ||
204 | /** | |
205 | * Sets the base JNDI environment template to use when acquiring LDAP connections. It is typically more common | |
206 | * to use the other get/set methods in this class to set individual environment settings rather than use | |
207 | * this method, but it is available for advanced users that want full control over the base JNDI environment | |
208 | * settings. | |
209 | * <p/> | |
210 | * Note that this template only represents the base/default environment settings. It is then merged with | |
211 | * appropriate runtime values as necessary in the {@link #getLdapContext(Object, Object)} implementation. | |
212 | * The merged environment instance is what is used to acquire the connection ({@link LdapContext}) at runtime. | |
213 | * | |
214 | * @param env the base JNDI environment template to use when acquiring LDAP connections. | |
215 | */ | |
216 | @SuppressWarnings({"unchecked"}) | |
217 | public void setEnvironment(Map env) { | |
218 | 1 | this.environment = env; |
219 | 1 | } |
220 | ||
221 | /** | |
222 | * Returns the environment property value bound under the specified key. | |
223 | * | |
224 | * @param name the name of the environment property | |
225 | * @return the property value or {@code null} if the value has not been set. | |
226 | */ | |
227 | private Object getEnvironmentProperty(String name) { | |
228 | 23 | return this.environment.get(name); |
229 | } | |
230 | ||
231 | /** | |
232 | * Will apply the value to the environment attribute if and only if the value is not null or empty. If it is | |
233 | * null or empty, the corresponding environment attribute will be removed. | |
234 | * | |
235 | * @param name the environment property key | |
236 | * @param value the environment property value. A null/empty value will trigger removal. | |
237 | */ | |
238 | private void setEnvironmentProperty(String name, String value) { | |
239 | 79 | if (StringUtils.hasText(value)) { |
240 | 78 | this.environment.put(name, value); |
241 | } else { | |
242 | 1 | this.environment.remove(name); |
243 | } | |
244 | 79 | } |
245 | ||
246 | /** | |
247 | * Returns whether or not connection pooling should be used when possible and appropriate. This property is NOT | |
248 | * backed by the {@link #getEnvironment() environment template} like most other properties in this class. It | |
249 | * is a flag to indicate that pooling is preferred. The default value is {@code true}. | |
250 | * <p/> | |
251 | * However, pooling will only actually be enabled if this property is {@code true} <em>and</em> the connection | |
252 | * being created is for the {@link #getSystemUsername() systemUsername} user. Connection pooling is not used for | |
253 | * general authentication attempts by application end-users because the probability of re-use for that same | |
254 | * user-specific connection after an authentication attempt is extremely low. | |
255 | * <p/> | |
256 | * If this attribute is {@code true} and it has been determined that the connection is being made with the | |
257 | * {@link #getSystemUsername() systemUsername}, the | |
258 | * {@link #getLdapContext(Object, Object)} implementation will set the Sun/Oracle-specific | |
259 | * {@code com.sun.jndi.ldap.connect.pool} environment property to "{@code true}". This means setting | |
260 | * this property is only likely to work if using the Sun/Oracle default context factory class (i.e. not using | |
261 | * a custom {@link #getContextFactoryClassName() contextFactoryClassName}). | |
262 | * | |
263 | * @return whether or not connection pooling should be used when possible and appropriate | |
264 | */ | |
265 | public boolean isPoolingEnabled() { | |
266 | 9 | return poolingEnabled; |
267 | } | |
268 | ||
269 | /** | |
270 | * Sets whether or not connection pooling should be used when possible and appropriate. This property is NOT | |
271 | * a wrapper to the {@link #getEnvironment() environment template} like most other properties in this class. It | |
272 | * is a flag to indicate that pooling is preferred. The default value is {@code true}. | |
273 | * <p/> | |
274 | * However, pooling will only actually be enabled if this property is {@code true} <em>and</em> the connection | |
275 | * being created is for the {@link #getSystemUsername() systemUsername} user. Connection pooling is not used for | |
276 | * general authentication attempts by application end-users because the probability of re-use for that same | |
277 | * user-specific connection after an authentication attempt is extremely low. | |
278 | * <p/> | |
279 | * If this attribute is {@code true} and it has been determined that the connection is being made with the | |
280 | * {@link #getSystemUsername() systemUsername}, the | |
281 | * {@link #getLdapContext(Object, Object)} implementation will set the Sun/Oracle-specific | |
282 | * {@code com.sun.jndi.ldap.connect.pool} environment property to "{@code true}". This means setting | |
283 | * this property is only likely to work if using the Sun/Oracle default context factory class (i.e. not using | |
284 | * a custom {@link #getContextFactoryClassName() contextFactoryClassName}). | |
285 | * | |
286 | * @param poolingEnabled whether or not connection pooling should be used when possible and appropriate | |
287 | */ | |
288 | public void setPoolingEnabled(boolean poolingEnabled) { | |
289 | 1 | this.poolingEnabled = poolingEnabled; |
290 | 1 | } |
291 | ||
292 | /** | |
293 | * Sets the LDAP referral behavior when creating a connection. Defaults to {@code follow}. See the Sun/Oracle LDAP | |
294 | * <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">referral documentation</a> for more. | |
295 | * | |
296 | * @param referral the referral property. | |
297 | * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">Referrals in JNDI</a> | |
298 | */ | |
299 | public void setReferral(String referral) { | |
300 | 34 | setEnvironmentProperty(Context.REFERRAL, referral); |
301 | 34 | } |
302 | ||
303 | /** | |
304 | * Returns the LDAP referral behavior when creating a connection. Defaults to {@code follow}. | |
305 | * See the Sun/Oracle LDAP | |
306 | * <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">referral documentation</a> for more. | |
307 | * | |
308 | * @return the LDAP referral behavior when creating a connection. | |
309 | * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/referral/jndi.html">Referrals in JNDI</a> | |
310 | */ | |
311 | public String getReferral() { | |
312 | 1 | return (String) getEnvironmentProperty(Context.REFERRAL); |
313 | } | |
314 | ||
315 | /** | |
316 | * The LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>). This must be configured. | |
317 | * | |
318 | * @param url the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>) | |
319 | */ | |
320 | public void setUrl(String url) { | |
321 | 9 | setEnvironmentProperty(Context.PROVIDER_URL, url); |
322 | 9 | } |
323 | ||
324 | /** | |
325 | * Returns the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>). | |
326 | * This must be configured. | |
327 | * | |
328 | * @return the LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>) | |
329 | */ | |
330 | public String getUrl() { | |
331 | 10 | return (String) getEnvironmentProperty(Context.PROVIDER_URL); |
332 | } | |
333 | ||
334 | /** | |
335 | * Sets the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an | |
336 | * LDAP connection used for authorization queries. | |
337 | * <p/> | |
338 | * Note that setting this property is not required if the calling LDAP Realm does not perform authorization | |
339 | * checks. | |
340 | * | |
341 | * @param systemPassword the password of the {@link #setSystemUsername(String) systemUsername} that will be used | |
342 | * when creating an LDAP connection used for authorization queries. | |
343 | */ | |
344 | public void setSystemPassword(String systemPassword) { | |
345 | 2 | this.systemPassword = systemPassword; |
346 | 2 | } |
347 | ||
348 | /** | |
349 | * Returns the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an | |
350 | * LDAP connection used for authorization queries. | |
351 | * <p/> | |
352 | * Note that setting this property is not required if the calling LDAP Realm does not perform authorization | |
353 | * checks. | |
354 | * | |
355 | * @return the password of the {@link #setSystemUsername(String) systemUsername} that will be used when creating an | |
356 | * LDAP connection used for authorization queries. | |
357 | */ | |
358 | public String getSystemPassword() { | |
359 | 2 | return this.systemPassword; |
360 | } | |
361 | ||
362 | /** | |
363 | * Sets the system username that will be used when creating an LDAP connection used for authorization queries. | |
364 | * The user must have the ability to query for authorization data for any application user. | |
365 | * <p/> | |
366 | * Note that setting this property is not required if the calling LDAP Realm does not perform authorization | |
367 | * checks. | |
368 | * | |
369 | * @param systemUsername the system username that will be used when creating an LDAP connection used for | |
370 | * authorization queries. | |
371 | */ | |
372 | public void setSystemUsername(String systemUsername) { | |
373 | 2 | this.systemUsername = systemUsername; |
374 | 2 | } |
375 | ||
376 | /** | |
377 | * Returns the system username that will be used when creating an LDAP connection used for authorization queries. | |
378 | * The user must have the ability to query for authorization data for any application user. | |
379 | * <p/> | |
380 | * Note that setting this property is not required if the calling LDAP Realm does not perform authorization | |
381 | * checks. | |
382 | * | |
383 | * @return the system username that will be used when creating an LDAP connection used for authorization queries. | |
384 | */ | |
385 | public String getSystemUsername() { | |
386 | 10 | return systemUsername; |
387 | } | |
388 | ||
389 | /*-------------------------------------------- | |
390 | | M E T H O D S | | |
391 | ============================================*/ | |
392 | ||
393 | /** | |
394 | * This implementation delegates to {@link #getLdapContext(Object, Object)} using the | |
395 | * {@link #getSystemUsername() systemUsername} and {@link #getSystemPassword() systemPassword} properties as | |
396 | * arguments. | |
397 | * | |
398 | * @return the system LdapContext | |
399 | * @throws NamingException if there is a problem connecting to the LDAP directory | |
400 | */ | |
401 | public LdapContext getSystemLdapContext() throws NamingException { | |
402 | 2 | return getLdapContext((Object)getSystemUsername(), getSystemPassword()); |
403 | } | |
404 | ||
405 | /** | |
406 | * Deprecated - use {@link #getLdapContext(Object, Object)} instead. This will be removed before Apache Shiro 2.0. | |
407 | * | |
408 | * @param username the username to use when creating the connection. | |
409 | * @param password the password to use when creating the connection. | |
410 | * @return a {@code LdapContext} bound using the given username and password. | |
411 | * @throws javax.naming.NamingException if there is an error creating the context. | |
412 | * @deprecated the {@link #getLdapContext(Object, Object)} method should be used in all cases to ensure more than | |
413 | * String principals and credentials can be used. Shiro no longer calls this method - it will be | |
414 | * removed before the 2.0 release. | |
415 | */ | |
416 | @Deprecated | |
417 | public LdapContext getLdapContext(String username, String password) throws NamingException { | |
418 | 1 | return getLdapContext((Object) username, password); |
419 | } | |
420 | ||
421 | /** | |
422 | * Returns {@code true} if LDAP connection pooling should be used when acquiring a connection based on the specified | |
423 | * account principal, {@code false} otherwise. | |
424 | * <p/> | |
425 | * This implementation returns {@code true} only if {@link #isPoolingEnabled()} and the principal equals the | |
426 | * {@link #getSystemUsername()}. The reasoning behind this is that connection pooling is not desirable for | |
427 | * general authentication attempts by application end-users because the probability of re-use for that same | |
428 | * user-specific connection after an authentication attempt is extremely low. | |
429 | * | |
430 | * @param principal the principal under which the connection will be made | |
431 | * @return {@code true} if LDAP connection pooling should be used when acquiring a connection based on the specified | |
432 | * account principal, {@code false} otherwise. | |
433 | */ | |
434 | protected boolean isPoolingConnections(Object principal) { | |
435 | 9 | return isPoolingEnabled() && principal != null && principal.equals(getSystemUsername()); |
436 | } | |
437 | ||
438 | /** | |
439 | * This implementation returns an LdapContext based on the configured JNDI/LDAP environment configuration. | |
440 | * The environnmet (Map) used at runtime is created by merging the default/configured | |
441 | * {@link #getEnvironment() environment template} with some runtime values as necessary (e.g. a principal and | |
442 | * credential available at runtime only). | |
443 | * <p/> | |
444 | * After the merged Map instance is created, the LdapContext connection is | |
445 | * {@link #createLdapContext(java.util.Hashtable) created} and returned. | |
446 | * | |
447 | * @param principal the principal to use when acquiring a connection to the LDAP directory | |
448 | * @param credentials the credentials (password, X.509 certificate, etc) to use when acquiring a connection to the | |
449 | * LDAP directory | |
450 | * @return the acquired {@code LdapContext} connection bound using the specified principal and credentials. | |
451 | * @throws NamingException | |
452 | * @throws IllegalStateException | |
453 | */ | |
454 | public LdapContext getLdapContext(Object principal, Object credentials) throws NamingException, | |
455 | IllegalStateException { | |
456 | ||
457 | 10 | String url = getUrl(); |
458 | 10 | if (url == null) { |
459 | 1 | throw new IllegalStateException("An LDAP URL must be specified of the form ldap://<hostname>:<port>"); |
460 | } | |
461 | ||
462 | //copy the environment template into the runtime instance that will be further edited based on | |
463 | //the method arguments and other class attributes. | |
464 | 9 | Hashtable<String, Object> env = new Hashtable<String, Object>(this.environment); |
465 | ||
466 | 9 | Object authcMech = getAuthenticationMechanism(); |
467 | 9 | if (authcMech == null && (principal != null || credentials != null)) { |
468 | //authenticationMechanism has not been set, but either a principal and/or credentials were | |
469 | //supplied, indicating that at least a 'simple' authentication attempt is indeed occurring - the Shiro | |
470 | //end-user just didn't configure it explicitly. So we set it to be 'simple' here as a convenience; | |
471 | //the Sun provider implementation already does this same logic, but by repeating that logic here, we ensure | |
472 | //this convenience exists regardless of provider implementation): | |
473 | 9 | env.put(Context.SECURITY_AUTHENTICATION, SIMPLE_AUTHENTICATION_MECHANISM_NAME); |
474 | } | |
475 | 9 | if (principal != null) { |
476 | 9 | env.put(Context.SECURITY_PRINCIPAL, principal); |
477 | } | |
478 | 9 | if (credentials != null) { |
479 | 8 | env.put(Context.SECURITY_CREDENTIALS, credentials); |
480 | } | |
481 | ||
482 | 9 | boolean pooling = isPoolingConnections(principal); |
483 | 9 | if (pooling) { |
484 | 1 | env.put(SUN_CONNECTION_POOLING_PROPERTY, "true"); |
485 | } | |
486 | ||
487 | 9 | if (log.isDebugEnabled()) { |
488 | 9 | log.debug("Initializing LDAP context using URL [{}] and principal [{}] with pooling {}", |
489 | new Object[]{url, principal, (pooling ? "enabled" : "disabled")}); | |
490 | } | |
491 | ||
492 | // validate the config before creating the context | |
493 | 9 | validateAuthenticationInfo(env); |
494 | ||
495 | 5 | return createLdapContext(env); |
496 | } | |
497 | ||
498 | /** | |
499 | * Creates and returns a new {@link javax.naming.ldap.InitialLdapContext} instance. This method exists primarily | |
500 | * to support testing where a mock LdapContext can be returned instead of actually creating a connection, but | |
501 | * subclasses are free to provide a different implementation if necessary. | |
502 | * | |
503 | * @param env the JNDI environment settings used to create the LDAP connection | |
504 | * @return an LdapConnection | |
505 | * @throws NamingException if a problem occurs creating the connection | |
506 | */ | |
507 | protected LdapContext createLdapContext(Hashtable env) throws NamingException { | |
508 | 1 | return new InitialLdapContext(env, null); |
509 | } | |
510 | ||
511 | ||
512 | /** | |
513 | * Validates the configuration in the JNDI <code>environment</code> settings and throws an exception if a problem | |
514 | * exists. | |
515 | * <p/> | |
516 | * This implementation will throw a {@link AuthenticationException} if the authentication mechanism is set to | |
517 | * 'simple', the principal is non-empty, and the credentials are empty (as per | |
518 | * <a href="http://tools.ietf.org/html/rfc4513#section-5.1.2">rfc4513 section-5.1.2</a>). | |
519 | * | |
520 | * @param environment the JNDI environment settings to be validated | |
521 | * @throws AuthenticationException if a configuration problem is detected | |
522 | */ | |
523 | protected void validateAuthenticationInfo(Hashtable<String, Object> environment) | |
524 | throws AuthenticationException | |
525 | { | |
526 | // validate when using Simple auth both principal and credentials are set | |
527 | 9 | if(SIMPLE_AUTHENTICATION_MECHANISM_NAME.equals(environment.get(Context.SECURITY_AUTHENTICATION))) { |
528 | ||
529 | // only validate credentials if we have a non-empty principal | |
530 | 9 | if( environment.get(Context.SECURITY_PRINCIPAL) != null && |
531 | StringUtils.hasText( String.valueOf( environment.get(Context.SECURITY_PRINCIPAL) ))) { | |
532 | ||
533 | 9 | Object credentials = environment.get(Context.SECURITY_CREDENTIALS); |
534 | ||
535 | // from the FAQ, we need to check for empty credentials: | |
536 | // http://docs.oracle.com/javase/tutorial/jndi/ldap/faq.html | |
537 | 9 | if( credentials == null || |
538 | (credentials instanceof byte[] && ((byte[])credentials).length <= 0) || // empty byte[] | |
539 | (credentials instanceof char[] && ((char[])credentials).length <= 0) || // empty char[] | |
540 | (String.class.isInstance(credentials) && !StringUtils.hasText(String.valueOf(credentials)))) { | |
541 | ||
542 | 4 | throw new javax.naming.AuthenticationException("LDAP Simple authentication requires both a " |
543 | + "principal and credentials."); | |
544 | } | |
545 | } | |
546 | } | |
547 | 5 | } |
548 | ||
549 | } |