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 JndiLdapContextFactory} 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 JndiLdapContextFactory}, 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    /*--------------------------------------------
056    |             C O N S T A N T S             |
057    ============================================*/
058
059    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLdapRealm.class);
060
061    /*--------------------------------------------
062    |    I N S T A N C E   V A R I A B L E S    |
063    ============================================*/
064
065    /**
066     * Defines the Suffix added to the User Principal Name when looking up groups (e.g. "memberOf")
067     * AD Example:
068     * User's Principal Name be "John.Doe"
069     * User's E-Mail Address be "John.Doe@example.com"
070     * For the example below, set:
071     * realm.principalSuffix = @example.com
072     * Only then, "John.Doe" and also "John.Doe@example.com" can authorize against groups
073     */
074    protected String principalSuffix;
075
076    protected String searchBase;
077
078    protected String url;
079
080    protected String systemUsername;
081
082    protected String systemPassword;
083
084    /**
085     * SHIRO-115 - prevent potential code injection.
086     */
087    protected String searchFilter = "(&(objectClass=*)(userPrincipalName={0}))";
088
089    private LdapContextFactory ldapContextFactory;
090
091    /*--------------------------------------------
092    |         C O N S T R U C T O R S           |
093    ============================================*/
094
095    /*--------------------------------------------
096    |  A C C E S S O R S / M O D I F I E R S    |
097    ============================================*/
098
099    /*--------------------------------------------
100    |               M E T H O D S               |
101    ============================================*/
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     */
110    public void setSearchBase(String searchBase) {
111        this.searchBase = searchBase;
112    }
113
114    /**
115     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
116     * <tt>LdapContextFactory</tt> is specified.
117     *
118     * @param url the LDAP url.
119     * @see JndiLdapContextFactory#setUrl(String)
120     */
121    public void setUrl(String url) {
122        this.url = url;
123    }
124
125    /**
126     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
127     * <tt>LdapContextFactory</tt> is specified.
128     *
129     * @param systemUsername the username to use when logging into the LDAP server for authorization.
130     * @see JndiLdapContextFactory#setSystemUsername(String)
131     */
132    public void setSystemUsername(String systemUsername) {
133        this.systemUsername = systemUsername;
134    }
135
136
137    /**
138     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
139     * <tt>LdapContextFactory</tt> is specified.
140     *
141     * @param systemPassword the password to use when logging into the LDAP server for authorization.
142     * @see JndiLdapContextFactory#setSystemPassword(String)
143     */
144    public void setSystemPassword(String systemPassword) {
145        this.systemPassword = systemPassword;
146    }
147
148
149    /**
150     * Configures the {@link LdapContextFactory} implementation that is used to create LDAP connections for
151     * authentication and authorization.  If this is set, the {@link LdapContextFactory} provided will be used.
152     * Otherwise, a {@link JndiLdapContextFactory} instance will be created based on the properties specified
153     * in this realm.
154     *
155     * @param ldapContextFactory the factory to use - if not specified, a default factory will be created automatically.
156     */
157    public void setLdapContextFactory(LdapContextFactory ldapContextFactory) {
158        this.ldapContextFactory = ldapContextFactory;
159    }
160
161
162    public void setSearchFilter(String searchFilter) {
163        this.searchFilter = searchFilter;
164    }
165
166    /*--------------------------------------------
167    |               M E T H O D S                |
168    ============================================*/
169
170    protected void onInit() {
171        super.onInit();
172        ensureContextFactory();
173    }
174
175    private LdapContextFactory ensureContextFactory() {
176        if (this.ldapContextFactory == null) {
177
178            if (LOGGER.isDebugEnabled()) {
179                LOGGER.debug("No LdapContextFactory specified - creating a default instance.");
180            }
181
182            JndiLdapContextFactory defaultFactory = new JndiLdapContextFactory();
183            defaultFactory.setUrl(this.url);
184            defaultFactory.setSystemUsername(this.systemUsername);
185            defaultFactory.setSystemPassword(this.systemPassword);
186
187            this.ldapContextFactory = defaultFactory;
188        }
189        return this.ldapContextFactory;
190    }
191
192
193    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
194        AuthenticationInfo info;
195        try {
196            info = queryForAuthenticationInfo(token, ensureContextFactory());
197        } catch (javax.naming.AuthenticationException e) {
198            throw new AuthenticationException("LDAP authentication failed.", e);
199        } catch (NamingException e) {
200            String msg = "LDAP naming error while attempting to authenticate user.";
201            throw new AuthenticationException(msg, e);
202        }
203
204        return info;
205    }
206
207
208    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
209        AuthorizationInfo info;
210        try {
211            info = queryForAuthorizationInfo(principals, ensureContextFactory());
212        } catch (NamingException e) {
213            String msg = "LDAP naming error while attempting to retrieve authorization for user [" + principals + "].";
214            throw new AuthorizationException(msg, e);
215        }
216
217        return info;
218    }
219
220
221    /**
222     * <p>Abstract method that should be implemented by subclasses to builds an
223     * {@link AuthenticationInfo} object by querying the LDAP context for the
224     * specified username.</p>
225     *
226     * @param token              the authentication token given during authentication.
227     * @param ldapContextFactory factory used to retrieve LDAP connections.
228     * @return an {@link AuthenticationInfo} instance containing information retrieved from the LDAP server.
229     * @throws NamingException if any LDAP errors occur during the search.
230     */
231    protected abstract AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token,
232                                                                 LdapContextFactory ldapContextFactory) throws NamingException;
233
234    /**
235     * <p>Abstract method that should be implemented by subclasses to builds an
236     * {@link AuthorizationInfo} object by querying the LDAP context for the
237     * specified principal.</p>
238     *
239     * @param principal          the principal of the Subject whose AuthenticationInfo should be queried from the LDAP server.
240     * @param ldapContextFactory factory used to retrieve LDAP connections.
241     * @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server.
242     * @throws NamingException if any LDAP errors occur during the search.
243     */
244    protected abstract AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principal,
245                                                               LdapContextFactory ldapContextFactory) throws NamingException;
246
247}