001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.shiro.realm.ldap;
020
021import java.util.Hashtable;
022import java.util.Map;
023import javax.naming.Context;
024import javax.naming.NamingException;
025import javax.naming.ldap.InitialLdapContext;
026import javax.naming.ldap.LdapContext;
027
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * <p>Default implementation of {@link LdapContextFactory} that can be configured or extended to
033 * customize the way {@link javax.naming.ldap.LdapContext} objects are retrieved.</p>
034 * <p/>
035 * <p>This implementation of {@link LdapContextFactory} is used by the {@link AbstractLdapRealm} if a
036 * factory is not explictly configured.</p>
037 * <p/>
038 * <p>Connection pooling is enabled by default on this factory, but can be disabled using the
039 * {@link #usePooling} property.</p>
040 *
041 * @since 0.2
042 * @deprecated replaced by the {@link JndiLdapContextFactory} implementation.  This implementation will be removed
043 * prior to Shiro 2.0
044 */
045@Deprecated
046public class DefaultLdapContextFactory implements LdapContextFactory {
047
048    //TODO - complete JavaDoc
049
050    /*--------------------------------------------
051    |             C O N S T A N T S             |
052    ============================================*/
053    /**
054     * The Sun LDAP property used to enable connection pooling.  This is used in the default implementation
055     * to enable LDAP connection pooling.
056     */
057    protected static final String SUN_CONNECTION_POOLING_PROPERTY = "com.sun.jndi.ldap.connect.pool";
058
059    /*--------------------------------------------
060    |    I N S T A N C E   V A R I A B L E S    |
061    ============================================*/
062
063    private static final Logger log = LoggerFactory.getLogger(DefaultLdapContextFactory.class);
064
065    protected String authentication = "simple";
066
067    protected String principalSuffix = null;
068
069    protected String searchBase = null;
070
071    protected String contextFactoryClassName = "com.sun.jndi.ldap.LdapCtxFactory";
072
073    protected String url = null;
074
075    protected String referral = "follow";
076
077    protected String systemUsername = null;
078
079    protected String systemPassword = null;
080
081    private boolean usePooling = true;
082
083    private Map<String, String> additionalEnvironment;
084
085    /*--------------------------------------------
086    |         C O N S T R U C T O R S           |
087    ============================================*/
088
089    /*--------------------------------------------
090    |  A C C E S S O R S / M O D I F I E R S    |
091    ============================================*/
092
093    /**
094     * Sets the type of LDAP authentication to perform when connecting to the LDAP server.  Defaults to "simple"
095     *
096     * @param authentication the type of LDAP authentication to perform.
097     */
098    public void setAuthentication(String authentication) {
099        this.authentication = authentication;
100    }
101
102    /**
103     * A suffix appended to the username. This is typically for
104     * domain names.  (e.g. "@MyDomain.local")
105     *
106     * @param principalSuffix the suffix.
107     */
108    public void setPrincipalSuffix(String principalSuffix) {
109        this.principalSuffix = principalSuffix;
110    }
111
112    /**
113     * The search base for the search to perform in the LDAP server.
114     * (e.g. OU=OrganizationName,DC=MyDomain,DC=local )
115     *
116     * @param searchBase the search base.
117     * @deprecated this attribute existed, but was never used in Shiro 1.x.  It will be removed prior to Shiro 2.0.
118     */
119    @Deprecated
120    public void setSearchBase(String searchBase) {
121        this.searchBase = searchBase;
122    }
123
124    /**
125     * The context factory to use. This defaults to the SUN LDAP JNDI implementation
126     * but can be overridden to use custom LDAP factories.
127     *
128     * @param contextFactoryClassName the context factory that should be used.
129     */
130    public void setContextFactoryClassName(String contextFactoryClassName) {
131        this.contextFactoryClassName = contextFactoryClassName;
132    }
133
134    /**
135     * The LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>)
136     *
137     * @param url the LDAP url.
138     */
139    public void setUrl(String url) {
140        this.url = url;
141    }
142
143    /**
144     * Sets the LDAP referral property.  Defaults to "follow"
145     *
146     * @param referral the referral property.
147     */
148    public void setReferral(String referral) {
149        this.referral = referral;
150    }
151
152    /**
153     * The system username that will be used when connecting to the LDAP server to retrieve authorization
154     * information about a user.  This must be specified for LDAP authorization to work, but is not required for
155     * only authentication.
156     *
157     * @param systemUsername the username to use when logging into the LDAP server for authorization.
158     */
159    public void setSystemUsername(String systemUsername) {
160        this.systemUsername = systemUsername;
161    }
162
163
164    /**
165     * The system password that will be used when connecting to the LDAP server to retrieve authorization
166     * information about a user.  This must be specified for LDAP authorization to work, but is not required for
167     * only authentication.
168     *
169     * @param systemPassword the password to use when logging into the LDAP server for authorization.
170     */
171    public void setSystemPassword(String systemPassword) {
172        this.systemPassword = systemPassword;
173    }
174
175    /**
176     * Determines whether or not LdapContext pooling is enabled for connections made using the system
177     * user account.  In the default implementation, this simply
178     * sets the <tt>com.sun.jndi.ldap.connect.pool</tt> property in the LDAP context environment.  If you use an
179     * LDAP Context Factory that is not Sun's default implementation, you will need to override the
180     * default behavior to use this setting in whatever way your underlying LDAP ContextFactory
181     * supports.  By default, pooling is enabled.
182     *
183     * @param usePooling true to enable pooling, or false to disable it.
184     */
185    public void setUsePooling(boolean usePooling) {
186        this.usePooling = usePooling;
187    }
188
189    /**
190     * These entries are added to the environment map before initializing the LDAP context.
191     *
192     * @param additionalEnvironment additional environment entries to be configured on the LDAP context.
193     */
194    public void setAdditionalEnvironment(Map<String, String> additionalEnvironment) {
195        this.additionalEnvironment = additionalEnvironment;
196    }
197
198    /*--------------------------------------------
199    |               M E T H O D S               |
200    ============================================*/
201    public LdapContext getSystemLdapContext() throws NamingException {
202        return getLdapContext(systemUsername, systemPassword);
203    }
204
205    /**
206     * Deprecated - use {@link #getLdapContext(Object, Object)} instead.  This will be removed before Apache Shiro 2.0.
207     *
208     * @param username the username to use when creating the connection.
209     * @param password the password to use when creating the connection.
210     * @return a {@code LdapContext} bound using the given username and password.
211     * @throws javax.naming.NamingException if there is an error creating the context.
212     * @deprecated the {@link #getLdapContext(Object, Object)} method should be used in all cases to ensure more than
213     *             String principals and credentials can be used.  Shiro no longer calls this method - it will be
214     *             removed before the 2.0 release.
215     */
216    @Deprecated
217    public LdapContext getLdapContext(String username, String password) throws NamingException {
218        if (username != null && principalSuffix != null) {
219            username += principalSuffix;
220        }
221        return getLdapContext((Object) username, password);
222    }
223
224    public LdapContext getLdapContext(Object principal, Object credentials) throws NamingException {
225        if (url == null) {
226            throw new IllegalStateException("An LDAP URL must be specified of the form ldap://<hostname>:<port>");
227        }
228
229        Hashtable<String, Object> env = new Hashtable<String, Object>();
230
231        env.put(Context.SECURITY_AUTHENTICATION, authentication);
232        if (principal != null) {
233            env.put(Context.SECURITY_PRINCIPAL, principal);
234        }
235        if (credentials!= null) {
236            env.put(Context.SECURITY_CREDENTIALS, credentials);
237        }
238        env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactoryClassName);
239        env.put(Context.PROVIDER_URL, url);
240        env.put(Context.REFERRAL, referral);
241
242        // Only pool connections for system contexts
243        if (usePooling && principal != null && principal.equals(systemUsername)) {
244            // Enable connection pooling
245            env.put(SUN_CONNECTION_POOLING_PROPERTY, "true");
246        }
247
248        if (additionalEnvironment != null) {
249            env.putAll(additionalEnvironment);
250        }
251
252        if (log.isDebugEnabled()) {
253            log.debug("Initializing LDAP context using URL [" + url + "] and username [" + systemUsername + "] " +
254                    "with pooling [" + (usePooling ? "enabled" : "disabled") + "]");
255        }
256
257        return new InitialLdapContext(env, null);
258    }
259}