File: DefaultInitialDirContextFactory.java

package info (click to toggle)
libspring-security-2.0-java 2.0.7.RELEASE-3
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 5,212 kB
  • ctags: 5,397
  • sloc: java: 25,240; xml: 6,845; sql: 165; perl: 64; makefile: 23; sh: 19
file content (368 lines) | stat: -rw-r--r-- 14,078 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.ldap;

import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.BadCredentialsException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;

import org.springframework.util.Assert;
import org.springframework.ldap.UncategorizedLdapException;
import org.springframework.ldap.core.support.DefaultDirObjectFactory;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.dao.DataAccessException;

import java.util.Hashtable;
import java.util.Map;
import java.util.StringTokenizer;

import javax.naming.CommunicationException;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.OperationNotSupportedException;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;


/**
 * Encapsulates the information for connecting to an LDAP server and provides an access point for obtaining
 * <tt>DirContext</tt> references.
 * <p>
 * The directory location is configured using by setting the constructor argument
 * <tt>providerUrl</tt>. This should be in the form <tt>ldap://monkeymachine.co.uk:389/dc=springframework,dc=org</tt>.
 * The Sun JNDI provider also supports lists of space-separated URLs, each of which will be tried in turn until a
 * connection is obtained.
 * </p>
 *  <p>To obtain an initial context, the client calls the <tt>newInitialDirContext</tt> method. There are two
 * signatures - one with no arguments and one which allows binding with a specific username and password.
 * </p>
 *  <p>The no-args version will bind anonymously unless a manager login has been configured using the properties
 * <tt>managerDn</tt> and <tt>managerPassword</tt>, in which case it will bind as the manager user.</p>
 *  <p>Connection pooling is enabled by default for anonymous or manager connections, but not when binding as a
 * specific user.</p>
 *
 * @author Robert Sanders
 * @author Luke Taylor
 * @version $Id$
 *
 *
 * @deprecated use {@link DefaultSpringSecurityContextSource} instead.
 *
 * @see <a href="http://java.sun.com/products/jndi/tutorial/ldap/connect/pool.html">The Java tutorial's guide to LDAP
 *      connection pooling</a>
 */
public class DefaultInitialDirContextFactory implements InitialDirContextFactory,
        SpringSecurityContextSource, MessageSourceAware {
    //~ Static fields/initializers =====================================================================================

    private static final Log logger = LogFactory.getLog(DefaultInitialDirContextFactory.class);
    private static final String CONNECTION_POOL_KEY = "com.sun.jndi.ldap.connect.pool";
    private static final String AUTH_TYPE_NONE = "none";

    //~ Instance fields ================================================================================================

    /** Allows extra environment variables to be added at config time. */
    private Map extraEnvVars = null;
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    /** Type of authentication within LDAP; default is simple. */
    private String authenticationType = "simple";

    /**
     * The INITIAL_CONTEXT_FACTORY used to create the JNDI Factory. Default is
     * "com.sun.jndi.ldap.LdapCtxFactory"; you <b>should not</b> need to set this unless you have unusual needs.
     */
    private String initialContextFactory = "com.sun.jndi.ldap.LdapCtxFactory";

    private String dirObjectFactoryClass = DefaultDirObjectFactory.class.getName();

    /**
     * If your LDAP server does not allow anonymous searches then you will need to provide a "manager" user's
     * DN to log in with.
     */
    private String managerDn = null;

    /** The manager user's password. */
    private String managerPassword = "manager_password_not_set";

    /** The LDAP url of the server (and root context) to connect to. */
    private String providerUrl;

    /**
     * The root DN. This is worked out from the url. It is used by client classes when forming a full DN for
     * bind authentication (for example).
     */
    private String rootDn = null;

    /**
     * Use the LDAP Connection pool; if true, then the LDAP environment property
     * "com.sun.jndi.ldap.connect.pool" is added to any other JNDI properties.
     */
    private boolean useConnectionPool = true;

    /** Set to true for ldap v3 compatible servers */
    private boolean useLdapContext = false;

    //~ Constructors ===================================================================================================

    /**
     * Create and initialize an instance to the LDAP url provided
     *
     * @param providerUrl a String of the form <code>ldap://localhost:389/base_dn<code>
     */
    public DefaultInitialDirContextFactory(String providerUrl) {
        this.setProviderUrl(providerUrl);
    }

    //~ Methods ========================================================================================================

    /**
     * Set the LDAP url
     *
     * @param providerUrl a String of the form <code>ldap://localhost:389/base_dn<code>
     */
    private void setProviderUrl(String providerUrl) {
        Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");

        this.providerUrl = providerUrl;

        StringTokenizer st = new StringTokenizer(providerUrl);

        // Work out rootDn from the first URL and check that the other URLs (if any) match
        while (st.hasMoreTokens()) {
            String url = st.nextToken();
            String urlRootDn = LdapUtils.parseRootDnFromUrl(url);

            logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");

            if (rootDn == null) {
                rootDn = urlRootDn;
            } else if (!rootDn.equals(urlRootDn)) {
                throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs");
            }
        }

        // This doesn't necessarily hold for embedded servers.
        //Assert.isTrue(uri.getScheme().equals("ldap"), "Ldap URL must start with 'ldap://'");
    }

    /**
     * Get the LDAP url
     *
     * @return the url
     */
    private String getProviderUrl() {
        return providerUrl;
    }

    private InitialDirContext connect(Hashtable env) {
        if (logger.isDebugEnabled()) {
            Hashtable envClone = (Hashtable) env.clone();

            if (envClone.containsKey(Context.SECURITY_CREDENTIALS)) {
                envClone.put(Context.SECURITY_CREDENTIALS, "******");
            }

            logger.debug("Creating InitialDirContext with environment " + envClone);
        }

        try {
            return useLdapContext ? new InitialLdapContext(env, null) : new InitialDirContext(env);
        } catch (NamingException ne) {
            if ((ne instanceof javax.naming.AuthenticationException)
                    || (ne instanceof OperationNotSupportedException)) {
                throw new BadCredentialsException(messages.getMessage("DefaultIntitalDirContextFactory.badCredentials",
                        "Bad credentials"), ne);
            }

            if (ne instanceof CommunicationException) {
                throw new UncategorizedLdapException(messages.getMessage(
                        "DefaultIntitalDirContextFactory.communicationFailure", "Unable to connect to LDAP server"), ne);
            }

            throw new UncategorizedLdapException(messages.getMessage(
                    "DefaultIntitalDirContextFactory.unexpectedException",
                    "Failed to obtain InitialDirContext due to unexpected exception"), ne);
        }
    }

    /**
     * Sets up the environment parameters for creating a new context.
     *
     * @return the Hashtable describing the base DirContext that will be created, minus the username/password if any.
     */
    protected Hashtable getEnvironment() {
        Hashtable env = new Hashtable();

        env.put(Context.SECURITY_AUTHENTICATION, authenticationType);
        env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactory);
        env.put(Context.PROVIDER_URL, getProviderUrl());

        if (useConnectionPool) {
            env.put(CONNECTION_POOL_KEY, "true");
        }

        if ((extraEnvVars != null) && (extraEnvVars.size() > 0)) {
            env.putAll(extraEnvVars);
        }

        return env;
    }

    /**
     * Returns the root DN of the configured provider URL. For example, if the URL is
     * <tt>ldap://monkeymachine.co.uk:389/dc=springframework,dc=org</tt> the value will be
     * <tt>dc=springframework,dc=org</tt>.
     *
     * @return the root DN calculated from the path of the LDAP url.
     */
    public String getRootDn() {
        return rootDn;
    }

    /**
     * Connects anonymously unless a manager user has been specified, in which case it will bind as the
     * manager.
     *
     * @return the resulting context object.
     */
    public DirContext newInitialDirContext() {
        if (managerDn != null) {
            return newInitialDirContext(managerDn, managerPassword);
        }

        Hashtable env = getEnvironment();
        env.put(Context.SECURITY_AUTHENTICATION, AUTH_TYPE_NONE);

        return connect(env);
    }

    public DirContext newInitialDirContext(String username, String password) {
        Hashtable env = getEnvironment();

        // Don't pool connections for individual users
        if (!username.equals(managerDn)) {
            env.remove(CONNECTION_POOL_KEY);
        }

        env.put(Context.SECURITY_PRINCIPAL, username);
        env.put(Context.SECURITY_CREDENTIALS, password);

        if(dirObjectFactoryClass != null) {
            env.put(Context.OBJECT_FACTORIES, dirObjectFactoryClass);
        }

        return connect(env);
    }

    /** Spring LDAP <tt>ContextSource</tt> method */
    public DirContext getReadOnlyContext() throws DataAccessException {
        return newInitialDirContext();
    }

    /** Spring LDAP <tt>ContextSource</tt> method */
    public DirContext getReadWriteContext() throws DataAccessException {
        return newInitialDirContext();
    }

    public void setAuthenticationType(String authenticationType) {
        Assert.hasLength(authenticationType, "LDAP Authentication type must not be empty or null");
        this.authenticationType = authenticationType;
    }

    /**
     * Sets any custom environment variables which will be added to the those returned
     * by the <tt>getEnvironment</tt> method.
     *
     * @param extraEnvVars extra environment variables to be added at config time.
     */
    public void setExtraEnvVars(Map extraEnvVars) {
        Assert.notNull(extraEnvVars, "Extra environment map cannot be null.");
        this.extraEnvVars = extraEnvVars;
    }

    public void setInitialContextFactory(String initialContextFactory) {
        Assert.hasLength(initialContextFactory, "Initial context factory name cannot be empty or null");
        this.initialContextFactory = initialContextFactory;
    }

    /**
     * Sets the directory user to authenticate as when obtaining a context using the
     * <tt>newInitialDirContext()</tt> method.
     * If no name is supplied then the context will be obtained anonymously.
     *
     * @param managerDn The name of the "manager" user for default authentication.
     */
    public void setManagerDn(String managerDn) {
        Assert.hasLength(managerDn, "Manager user name  cannot be empty or null.");
        this.managerDn = managerDn;
    }

    /**
     * Sets the password which will be used in combination with the manager DN.
     *
     * @param managerPassword The "manager" user's password.
     */
    public void setManagerPassword(String managerPassword) {
        Assert.hasLength(managerPassword, "Manager password must not be empty or null.");
        this.managerPassword = managerPassword;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

    /**
     * Connection pooling is enabled by default for anonymous or "manager" connections when using the default
     * Sun provider. To disable all connection pooling, set this property to false.
     *
     * @param useConnectionPool whether to pool connections for non-specific users.
     */
    public void setUseConnectionPool(boolean useConnectionPool) {
        this.useConnectionPool = useConnectionPool;
    }

    public void setUseLdapContext(boolean useLdapContext) {
        this.useLdapContext = useLdapContext;
    }

    public void setDirObjectFactory(String dirObjectFactory) {
        this.dirObjectFactoryClass = dirObjectFactory;
    }

    public DirContext getReadWriteContext(String userDn, Object credentials) {
        return newInitialDirContext(userDn, (String) credentials);
    }

    public DirContext getContext(String principal, String credentials) {
        throw new UnsupportedOperationException("DirContext");
    }

    public DistinguishedName getBaseLdapPath() {
        return new DistinguishedName(rootDn);
    }

    public String getBaseLdapPathAsString() {
        return getBaseLdapPath().toString();
    }
}