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     */
019    package org.apache.shiro.realm.ldap;
020    
021    import java.util.Hashtable;
022    import java.util.Map;
023    import javax.naming.Context;
024    import javax.naming.NamingException;
025    import javax.naming.ldap.InitialLdapContext;
026    import javax.naming.ldap.LdapContext;
027    
028    import org.slf4j.Logger;
029    import 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     *
035     * <p>This implementation of {@link LdapContextFactory} is used by the {@link AbstractLdapRealm} if a
036     * factory is not explictly configured.</p>
037     *
038     * <p>Connection pooling is enabled by default on this factory, but can be disabled using the
039     * {@link #usePooling} property.</p>
040     *
041     * @author Jeremy Haile
042     * @since 0.2
043     */
044    public class DefaultLdapContextFactory implements LdapContextFactory {
045    
046        //TODO - complete JavaDoc
047    
048        /*--------------------------------------------
049        |             C O N S T A N T S             |
050        ============================================*/
051        /**
052         * The Sun LDAP property used to enable connection pooling.  This is used in the default implementation
053         * to enable LDAP connection pooling.
054         */
055        protected static final String SUN_CONNECTION_POOLING_PROPERTY = "com.sun.jndi.ldap.connect.pool";
056    
057        /*--------------------------------------------
058        |    I N S T A N C E   V A R I A B L E S    |
059        ============================================*/
060    
061        private static final Logger log = LoggerFactory.getLogger(DefaultLdapContextFactory.class);
062    
063        protected String authentication = "simple";
064    
065        protected String principalSuffix = null;
066    
067        protected String searchBase = null;
068    
069        protected String contextFactoryClassName = "com.sun.jndi.ldap.LdapCtxFactory";
070    
071        protected String url = null;
072    
073        protected String referral = "follow";
074    
075        protected String systemUsername = null;
076    
077        protected String systemPassword = null;
078    
079        private boolean usePooling = true;
080    
081        private Map<String, String> additionalEnvironment;
082    
083        /*--------------------------------------------
084        |         C O N S T R U C T O R S           |
085        ============================================*/
086    
087        /*--------------------------------------------
088        |  A C C E S S O R S / M O D I F I E R S    |
089        ============================================*/
090    
091        /**
092         * Sets the type of LDAP authentication to perform when connecting to the LDAP server.  Defaults to "simple"
093         *
094         * @param authentication the type of LDAP authentication to perform.
095         */
096        public void setAuthentication(String authentication) {
097            this.authentication = authentication;
098        }
099    
100        /**
101         * A suffix appended to the username. This is typically for
102         * domain names.  (e.g. "@MyDomain.local")
103         *
104         * @param principalSuffix the suffix.
105         */
106        public void setPrincipalSuffix(String principalSuffix) {
107            this.principalSuffix = principalSuffix;
108        }
109    
110        /**
111         * The search base for the search to perform in the LDAP server.
112         * (e.g. OU=OrganizationName,DC=MyDomain,DC=local )
113         *
114         * @param searchBase the search base.
115         */
116        public void setSearchBase(String searchBase) {
117            this.searchBase = searchBase;
118        }
119    
120        /**
121         * The context factory to use. This defaults to the SUN LDAP JNDI implementation
122         * but can be overridden to use custom LDAP factories.
123         *
124         * @param contextFactoryClassName the context factory that should be used.
125         */
126        public void setContextFactoryClassName(String contextFactoryClassName) {
127            this.contextFactoryClassName = contextFactoryClassName;
128        }
129    
130        /**
131         * The LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>)
132         *
133         * @param url the LDAP url.
134         */
135        public void setUrl(String url) {
136            this.url = url;
137        }
138    
139        /**
140         * Sets the LDAP referral property.  Defaults to "follow"
141         *
142         * @param referral the referral property.
143         */
144        public void setReferral(String referral) {
145            this.referral = referral;
146        }
147    
148        /**
149         * The system username that will be used when connecting to the LDAP server to retrieve authorization
150         * information about a user.  This must be specified for LDAP authorization to work, but is not required for
151         * only authentication.
152         *
153         * @param systemUsername the username to use when logging into the LDAP server for authorization.
154         */
155        public void setSystemUsername(String systemUsername) {
156            this.systemUsername = systemUsername;
157        }
158    
159    
160        /**
161         * The system password that will be used when connecting to the LDAP server to retrieve authorization
162         * information about a user.  This must be specified for LDAP authorization to work, but is not required for
163         * only authentication.
164         *
165         * @param systemPassword the password to use when logging into the LDAP server for authorization.
166         */
167        public void setSystemPassword(String systemPassword) {
168            this.systemPassword = systemPassword;
169        }
170    
171        /**
172         * Determines whether or not LdapContext pooling is enabled for connections made using the system
173         * user account.  In the default implementation, this simply
174         * sets the <tt>com.sun.jndi.ldap.connect.pool</tt> property in the LDAP context environment.  If you use an
175         * LDAP Context Factory that is not Sun's default implementation, you will need to override the
176         * default behavior to use this setting in whatever way your underlying LDAP ContextFactory
177         * supports.  By default, pooling is enabled.
178         *
179         * @param usePooling true to enable pooling, or false to disable it.
180         */
181        public void setUsePooling(boolean usePooling) {
182            this.usePooling = usePooling;
183        }
184    
185        /**
186         * These entries are added to the environment map before initializing the LDAP context.
187         *
188         * @param additionalEnvironment additional environment entries to be configured on the LDAP context.
189         */
190        public void setAdditionalEnvironment(Map<String, String> additionalEnvironment) {
191            this.additionalEnvironment = additionalEnvironment;
192        }
193    
194        /*--------------------------------------------
195        |               M E T H O D S               |
196        ============================================*/
197    
198        public LdapContext getSystemLdapContext() throws NamingException {
199            return getLdapContext(systemUsername, systemPassword);
200        }
201    
202        public LdapContext getLdapContext(String username, String password) throws NamingException {
203            if (searchBase == null) {
204                throw new IllegalStateException("A search base must be specified.");
205            }
206            if (url == null) {
207                throw new IllegalStateException("An LDAP URL must be specified of the form ldap://<hostname>:<port>");
208            }
209    
210            if (username != null && principalSuffix != null) {
211                username += principalSuffix;
212            }
213    
214            Hashtable<String, String> env = new Hashtable<String, String>();
215    
216            env.put(Context.SECURITY_AUTHENTICATION, authentication);
217            if (username != null) {
218                env.put(Context.SECURITY_PRINCIPAL, username);
219            }
220            if (password != null) {
221                env.put(Context.SECURITY_CREDENTIALS, password);
222            }
223            env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactoryClassName);
224            env.put(Context.PROVIDER_URL, url);
225            env.put(Context.REFERRAL, referral);
226    
227            // Only pool connections for system contexts
228            if (usePooling && username != null && username.equals(systemUsername)) {
229                // Enable connection pooling
230                env.put(SUN_CONNECTION_POOLING_PROPERTY, "true");
231            }
232    
233            if (additionalEnvironment != null) {
234                env.putAll(additionalEnvironment);
235            }
236    
237            if (log.isDebugEnabled()) {
238                log.debug("Initializing LDAP context using URL [" + url + "] and username [" + systemUsername + "] " +
239                        "with pooling [" + (usePooling ? "enabled" : "disabled") + "]");
240            }
241    
242            return new InitialLdapContext(env, null);
243        }
244    }