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 org.apache.shiro.authc.AuthenticationException;
022    import org.apache.shiro.authc.AuthenticationInfo;
023    import org.apache.shiro.authc.AuthenticationToken;
024    import org.apache.shiro.authz.AuthorizationException;
025    import org.apache.shiro.authz.AuthorizationInfo;
026    import org.apache.shiro.realm.AuthorizingRealm;
027    import org.apache.shiro.subject.PrincipalCollection;
028    import org.slf4j.Logger;
029    import org.slf4j.LoggerFactory;
030    
031    import javax.naming.NamingException;
032    
033    /**
034     * <p>A {@link org.apache.shiro.realm.Realm} that authenticates with an LDAP
035     * server to build the Subject for a user.  This implementation only returns roles for a
036     * particular user, and not permissions - but it can be subclassed to build a permission
037     * list as well.</p>
038     *
039     * <p>Implementations would need to implement the
040     * {@link #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken ,LdapContextFactory)} and
041     * {@link #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection ,LdapContextFactory)} abstract methods.</p>
042     *
043     * <p>By default, this implementation will create an instance of {@link DefaultLdapContextFactory} to use for
044     * creating LDAP connections using the principalSuffix, searchBase, url, systemUsername, and systemPassword properties
045     * specified on the realm.  The remaining settings use the defaults of {@link DefaultLdapContextFactory}, which are usually
046     * sufficient.  If more customized connections are needed, you should inject a custom {@link LdapContextFactory}, which
047     * will cause these properties specified on the realm to be ignored.</p>
048     *
049     * @author Jeremy Haile
050     * @author Les Hazlewood
051     * @see #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken , LdapContextFactory)
052     * @see #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection , LdapContextFactory)
053     * @since 0.1
054     */
055    public abstract class AbstractLdapRealm extends AuthorizingRealm {
056    
057        //TODO - complete JavaDoc
058    
059        /*--------------------------------------------
060        |             C O N S T A N T S             |
061        ============================================*/
062    
063        private static final Logger log = LoggerFactory.getLogger(AbstractLdapRealm.class);
064    
065        /*--------------------------------------------
066        |    I N S T A N C E   V A R I A B L E S    |
067        ============================================*/
068        protected String principalSuffix = null;
069    
070        protected String searchBase = null;
071    
072        protected String url = null;
073    
074        protected String systemUsername = null;
075    
076        protected String systemPassword = null;
077    
078        private LdapContextFactory ldapContextFactory = null;
079    
080        /*--------------------------------------------
081        |         C O N S T R U C T O R S           |
082        ============================================*/
083    
084        /*--------------------------------------------
085        |  A C C E S S O R S / M O D I F I E R S    |
086        ============================================*/
087    
088        /*--------------------------------------------
089        |               M E T H O D S               |
090        ============================================*/
091    
092    
093        /**
094         * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
095         * <tt>LdapContextFactory</tt> is specified.
096         *
097         * @param principalSuffix the suffix.
098         * @see DefaultLdapContextFactory#setPrincipalSuffix(String)
099         */
100        public void setPrincipalSuffix(String principalSuffix) {
101            this.principalSuffix = principalSuffix;
102        }
103    
104        /**
105         * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
106         * <tt>LdapContextFactory</tt> is specified.
107         *
108         * @param searchBase the search base.
109         * @see DefaultLdapContextFactory#setSearchBase(String)
110         */
111        public void setSearchBase(String searchBase) {
112            this.searchBase = searchBase;
113        }
114    
115        /**
116         * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
117         * <tt>LdapContextFactory</tt> is specified.
118         *
119         * @param url the LDAP url.
120         * @see DefaultLdapContextFactory#setUrl(String)
121         */
122        public void setUrl(String url) {
123            this.url = url;
124        }
125    
126        /**
127         * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
128         * <tt>LdapContextFactory</tt> is specified.
129         *
130         * @param systemUsername the username to use when logging into the LDAP server for authorization.
131         * @see DefaultLdapContextFactory#setSystemUsername(String)
132         */
133        public void setSystemUsername(String systemUsername) {
134            this.systemUsername = systemUsername;
135        }
136    
137    
138        /**
139         * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
140         * <tt>LdapContextFactory</tt> is specified.
141         *
142         * @param systemPassword the password to use when logging into the LDAP server for authorization.
143         * @see DefaultLdapContextFactory#setSystemPassword(String)
144         */
145        public void setSystemPassword(String systemPassword) {
146            this.systemPassword = systemPassword;
147        }
148    
149    
150        /**
151         * Configures the {@link LdapContextFactory} implementation that is used to create LDAP connections for
152         * authentication and authorization.  If this is set, the {@link LdapContextFactory} provided will be used.
153         * Otherwise, a {@link DefaultLdapContextFactory} instance will be created based on the properties specified
154         * in this realm.
155         *
156         * @param ldapContextFactory the factory to use - if not specified, a default factory will be created automatically.
157         */
158        public void setLdapContextFactory(LdapContextFactory ldapContextFactory) {
159            this.ldapContextFactory = ldapContextFactory;
160        }
161    
162        /*--------------------------------------------
163        |               M E T H O D S                |
164        ============================================*/
165    
166        protected void onInit() {
167            ensureContextFactory();
168        }
169    
170        private LdapContextFactory ensureContextFactory() {
171            if (this.ldapContextFactory == null) {
172    
173                if (log.isDebugEnabled()) {
174                    log.debug("No LdapContextFactory specified - creating a default instance.");
175                }
176    
177                DefaultLdapContextFactory defaultFactory = new DefaultLdapContextFactory();
178                defaultFactory.setPrincipalSuffix(this.principalSuffix);
179                defaultFactory.setSearchBase(this.searchBase);
180                defaultFactory.setUrl(this.url);
181                defaultFactory.setSystemUsername(this.systemUsername);
182                defaultFactory.setSystemPassword(this.systemPassword);
183    
184                this.ldapContextFactory = defaultFactory;
185            }
186            return this.ldapContextFactory;
187        }
188    
189    
190        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
191            AuthenticationInfo info;
192            try {
193                info = queryForAuthenticationInfo(token, ensureContextFactory());
194            } catch (javax.naming.AuthenticationException e) {
195                throw new AuthenticationException("LDAP authentication failed.", e);
196            } catch (NamingException e) {
197                String msg = "LDAP naming error while attempting to authenticate user.";
198                throw new AuthenticationException(msg, e);
199            }
200    
201            return info;
202        }
203    
204    
205        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
206            AuthorizationInfo info;
207            try {
208                info = queryForAuthorizationInfo(principals, ensureContextFactory());
209            } catch (NamingException e) {
210                String msg = "LDAP naming error while attempting to retrieve authorization for user [" + principals + "].";
211                throw new AuthorizationException(msg, e);
212            }
213    
214            return info;
215        }
216    
217    
218        /**
219         * <p>Abstract method that should be implemented by subclasses to builds an
220         * {@link AuthenticationInfo} object by querying the LDAP context for the
221         * specified username.</p>
222         *
223         * @param token              the authentication token given during authentication.
224         * @param ldapContextFactory factory used to retrieve LDAP connections.
225         * @return an {@link AuthenticationInfo} instance containing information retrieved from the LDAP server.
226         * @throws NamingException if any LDAP errors occur during the search.
227         */
228        protected abstract AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException;
229    
230    
231        /**
232         * <p>Abstract method that should be implemented by subclasses to builds an
233         * {@link AuthorizationInfo} object by querying the LDAP context for the
234         * specified principal.</p>
235         *
236         * @param principal          the principal of the Subject whose AuthenticationInfo should be queried from the LDAP server.
237         * @param ldapContextFactory factory used to retrieve LDAP connections.
238         * @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server.
239         * @throws NamingException if any LDAP errors occur during the search.
240         */
241        protected abstract AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principal, LdapContextFactory ldapContextFactory) throws NamingException;
242    
243    }