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