blob: df64fdad70e4c52ce89182a6676da509fa892096 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../jacoco-resources/report.gif" type="image/gif"/><title>DefaultLdapRealm.java</title><link rel="stylesheet" href="../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../index.html" class="el_report">Apache Shiro :: Core</a> &gt; <a href="index.source.html" class="el_package">org.apache.shiro.realm.ldap</a> &gt; <span class="el_source">DefaultLdapRealm.java</span></div><h1>DefaultLdapRealm.java</h1><pre class="source lang-java linenums">/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* &quot;License&quot;); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* &quot;AS IS&quot; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.realm.ldap;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.ldap.UnsupportedAuthenticationMechanismException;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.AuthenticationNotSupportedException;
import javax.naming.NamingException;
import javax.naming.ldap.LdapContext;
/**
* An LDAP {@link org.apache.shiro.realm.Realm Realm} implementation utilizing Sun's/Oracle's
* &lt;a href=&quot;http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/jndi.html&quot;&gt;JNDI API as an LDAP API&lt;/a&gt;. This is
* Shiro's default implementation for supporting LDAP, as using the JNDI API has been a common approach for Java LDAP
* support for many years.
* &lt;p/&gt;
* This realm implementation and its backing {@link JndiLdapContextFactory} should cover 99% of all Shiro-related LDAP
* authentication and authorization needs. However, if it does not suit your needs, you might want to look into
* creating your own realm using an alternative, perhaps more robust, LDAP communication API, such as the
* &lt;a href=&quot;http://directory.apache.org/api/&quot;&gt;Apache LDAP API&lt;/a&gt;.
* &lt;h2&gt;Authentication&lt;/h2&gt;
* During an authentication attempt, if the submitted {@code AuthenticationToken}'s
* {@link org.apache.shiro.authc.AuthenticationToken#getPrincipal() principal} is a simple username, but the
* LDAP directory expects a complete User Distinguished Name (User DN) to establish a connection, the
* {@link #setUserDnTemplate(String) userDnTemplate} property must be configured. If not configured,
* the property will pass the simple username directly as the User DN, which is often incorrect in most LDAP
* environments (maybe Microsoft ActiveDirectory being the exception).
* &lt;h2&gt;Authorization&lt;/h2&gt;
* By default, authorization is effectively disabled due to the default
* {@link #doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)} implementation returning {@code null}.
* If you wish to perform authorization based on an LDAP schema, you must subclass this one
* and override that method to reflect your organization's data model.
* &lt;h2&gt;Configuration&lt;/h2&gt;
* This class primarily provides the {@link #setUserDnTemplate(String) userDnTemplate} property to allow you to specify
* the your LDAP server's User DN format. Most other configuration is performed via the nested
* {@link LdapContextFactory contextFactory} property.
* &lt;p/&gt;
* For example, defining this realm in Shiro .ini:
* &lt;pre&gt;
* [main]
* ldapRealm = org.apache.shiro.realm.ldap.DefaultLdapRealm
* ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
* ldapRealm.contextFactory.url = ldap://ldapHost:389
* ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5
* ldapRealm.contextFactory.environment[some.obscure.jndi.key] = some value
* ...
* &lt;/pre&gt;
* The default {@link #setContextFactory contextFactory} instance is a {@link JndiLdapContextFactory}. See that
* class's JavaDoc for more information on configuring the LDAP connection as well as specifying JNDI environment
* properties as necessary.
*
* @see JndiLdapContextFactory
*
* @since 1.3
*/
public class DefaultLdapRealm extends AuthorizingRealm {
<span class="fc" id="L86"> private static final Logger log = LoggerFactory.getLogger(DefaultLdapRealm.class);</span>
//The zero index currently means nothing, but could be utilized in the future for other substitution techniques.
private static final String USERDN_SUBSTITUTION_TOKEN = &quot;{0}&quot;;
private String userDnPrefix;
private String userDnSuffix;
/*--------------------------------------------
| I N S T A N C E V A R I A B L E S |
============================================*/
/**
* The LdapContextFactory instance used to acquire {@link javax.naming.ldap.LdapContext LdapContext}'s at runtime
* to acquire connections to the LDAP directory to perform authentication attempts and authorizatino queries.
*/
private LdapContextFactory contextFactory;
/*--------------------------------------------
| C O N S T R U C T O R S |
============================================*/
/**
* Default no-argument constructor that defaults the internal {@link LdapContextFactory} instance to a
* {@link JndiLdapContextFactory}.
*/
<span class="fc" id="L111"> public DefaultLdapRealm() {</span>
//Credentials Matching is not necessary - the LDAP directory will do it automatically:
<span class="fc" id="L113"> setCredentialsMatcher(new AllowAllCredentialsMatcher());</span>
//Any Object principal and Object credentials may be passed to the LDAP provider, so accept any token:
<span class="fc" id="L115"> setAuthenticationTokenClass(AuthenticationToken.class);</span>
<span class="fc" id="L116"> this.contextFactory = new JndiLdapContextFactory();</span>
<span class="fc" id="L117"> }</span>
/*--------------------------------------------
| A C C E S S O R S / M O D I F I E R S |
============================================*/
/**
* Returns the User DN prefix to use when building a runtime User DN value or {@code null} if no
* {@link #getUserDnTemplate() userDnTemplate} has been configured. If configured, this value is the text that
* occurs before the {@link #USERDN_SUBSTITUTION_TOKEN} in the {@link #getUserDnTemplate() userDnTemplate} value.
*
* @return the the User DN prefix to use when building a runtime User DN value or {@code null} if no
* {@link #getUserDnTemplate() userDnTemplate} has been configured.
*/
protected String getUserDnPrefix() {
<span class="fc" id="L132"> return userDnPrefix;</span>
}
/**
* Returns the User DN suffix to use when building a runtime User DN value. or {@code null} if no
* {@link #getUserDnTemplate() userDnTemplate} has been configured. If configured, this value is the text that
* occurs after the {@link #USERDN_SUBSTITUTION_TOKEN} in the {@link #getUserDnTemplate() userDnTemplate} value.
*
* @return the User DN suffix to use when building a runtime User DN value or {@code null} if no
* {@link #getUserDnTemplate() userDnTemplate} has been configured.
*/
protected String getUserDnSuffix() {
<span class="fc" id="L144"> return userDnSuffix;</span>
}
/*--------------------------------------------
| M E T H O D S |
============================================*/
/**
* Sets the User Distinguished Name (DN) template to use when creating User DNs at runtime. A User DN is an LDAP
* fully-qualified unique user identifier which is required to establish a connection with the LDAP
* directory to authenticate users and query for authorization information.
* &lt;h2&gt;Usage&lt;/h2&gt;
* User DN formats are unique to the LDAP directory's schema, and each environment differs - you will need to
* specify the format corresponding to your directory. You do this by specifying the full User DN as normal, but
* but you use a &lt;b&gt;{@code {0}}&lt;/b&gt; placeholder token in the string representing the location where the
* user's submitted principal (usually a username or uid) will be substituted at runtime.
* &lt;p/&gt;
* For example, if your directory
* uses an LDAP {@code uid} attribute to represent usernames, the User DN for the {@code jsmith} user may look like
* this:
* &lt;p/&gt;
* &lt;pre&gt;uid=jsmith,ou=users,dc=mycompany,dc=com&lt;/pre&gt;
* &lt;p/&gt;
* in which case you would set this property with the following template value:
* &lt;p/&gt;
* &lt;pre&gt;uid=&lt;b&gt;{0}&lt;/b&gt;,ou=users,dc=mycompany,dc=com&lt;/pre&gt;
* &lt;p/&gt;
* If no template is configured, the raw {@code AuthenticationToken}
* {@link AuthenticationToken#getPrincipal() principal} will be used as the LDAP principal. This is likely
* incorrect as most LDAP directories expect a fully-qualified User DN as opposed to the raw uid or username. So,
* ensure you set this property to match your environment!
*
* @param template the User Distinguished Name template to use for runtime substitution
* @throws IllegalArgumentException if the template is null, empty, or does not contain the
* {@code {0}} substitution token.
* @see LdapContextFactory#getLdapContext(Object,Object)
*/
public void setUserDnTemplate(String template) throws IllegalArgumentException {
<span class="fc bfc" id="L182" title="All 2 branches covered."> if (!StringUtils.hasText(template)) {</span>
<span class="fc" id="L183"> String msg = &quot;User DN template cannot be null or empty.&quot;;</span>
<span class="fc" id="L184"> throw new IllegalArgumentException(msg);</span>
}
<span class="fc" id="L186"> int index = template.indexOf(USERDN_SUBSTITUTION_TOKEN);</span>
<span class="fc bfc" id="L187" title="All 2 branches covered."> if (index &lt; 0) {</span>
<span class="fc" id="L188"> String msg = &quot;User DN template must contain the '&quot; +</span>
USERDN_SUBSTITUTION_TOKEN + &quot;' replacement token to understand where to &quot; +
&quot;insert the runtime authentication principal.&quot;;
<span class="fc" id="L191"> throw new IllegalArgumentException(msg);</span>
}
<span class="fc" id="L193"> String prefix = template.substring(0, index);</span>
<span class="fc" id="L194"> String suffix = template.substring(prefix.length() + USERDN_SUBSTITUTION_TOKEN.length());</span>
<span class="pc bpc" id="L195" title="1 of 2 branches missed."> if (log.isDebugEnabled()) {</span>
<span class="fc" id="L196"> log.debug(&quot;Determined user DN prefix [{}] and suffix [{}]&quot;, prefix, suffix);</span>
}
<span class="fc" id="L198"> this.userDnPrefix = prefix;</span>
<span class="fc" id="L199"> this.userDnSuffix = suffix;</span>
<span class="fc" id="L200"> }</span>
/**
* Returns the User Distinguished Name (DN) template to use when creating User DNs at runtime - see the
* {@link #setUserDnTemplate(String) setUserDnTemplate} JavaDoc for a full explanation.
*
* @return the User Distinguished Name (DN) template to use when creating User DNs at runtime.
*/
public String getUserDnTemplate() {
<span class="fc" id="L209"> return getUserDn(USERDN_SUBSTITUTION_TOKEN);</span>
}
/**
* Returns the LDAP User Distinguished Name (DN) to use when acquiring an
* {@link javax.naming.ldap.LdapContext LdapContext} from the {@link LdapContextFactory}.
* &lt;p/&gt;
* If the the {@link #getUserDnTemplate() userDnTemplate} property has been set, this implementation will construct
* the User DN by substituting the specified {@code principal} into the configured template. If the
* {@link #getUserDnTemplate() userDnTemplate} has not been set, the method argument will be returned directly
* (indicating that the submitted authentication token principal &lt;em&gt;is&lt;/em&gt; the User DN).
*
* @param principal the principal to substitute into the configured {@link #getUserDnTemplate() userDnTemplate}.
* @return the constructed User DN to use at runtime when acquiring an {@link javax.naming.ldap.LdapContext}.
* @throws IllegalArgumentException if the method argument is null or empty
* @throws IllegalStateException if the {@link #getUserDnTemplate userDnTemplate} has not been set.
* @see LdapContextFactory#getLdapContext(Object, Object)
*/
protected String getUserDn(String principal) throws IllegalArgumentException, IllegalStateException {
<span class="fc bfc" id="L228" title="All 2 branches covered."> if (!StringUtils.hasText(principal)) {</span>
<span class="fc" id="L229"> throw new IllegalArgumentException(&quot;User principal cannot be null or empty for User DN construction.&quot;);</span>
}
<span class="fc" id="L231"> String prefix = getUserDnPrefix();</span>
<span class="fc" id="L232"> String suffix = getUserDnSuffix();</span>
<span class="pc bpc" id="L233" title="1 of 4 branches missed."> if (prefix == null &amp;&amp; suffix == null) {</span>
<span class="fc" id="L234"> log.debug(&quot;userDnTemplate property has not been configured, indicating the submitted &quot; +</span>
&quot;AuthenticationToken's principal is the same as the User DN. Returning the method argument &quot; +
&quot;as is.&quot;);
<span class="fc" id="L237"> return principal;</span>
}
<span class="pc bpc" id="L240" title="1 of 2 branches missed."> int prefixLength = prefix != null ? prefix.length() : 0;</span>
<span class="pc bpc" id="L241" title="1 of 2 branches missed."> int suffixLength = suffix != null ? suffix.length() : 0;</span>
<span class="fc" id="L242"> StringBuilder sb = new StringBuilder(prefixLength + principal.length() + suffixLength);</span>
<span class="pc bpc" id="L243" title="1 of 2 branches missed."> if (prefixLength &gt; 0) {</span>
<span class="fc" id="L244"> sb.append(prefix);</span>
}
<span class="fc" id="L246"> sb.append(principal);</span>
<span class="pc bpc" id="L247" title="1 of 2 branches missed."> if (suffixLength &gt; 0) {</span>
<span class="fc" id="L248"> sb.append(suffix);</span>
}
<span class="fc" id="L250"> return sb.toString();</span>
}
/**
* Sets the LdapContextFactory instance used to acquire connections to the LDAP directory during authentication
* attempts and authorization queries. Unless specified otherwise, the default is a {@link JndiLdapContextFactory}
* instance.
*
* @param contextFactory the LdapContextFactory instance used to acquire connections to the LDAP directory during
* authentication attempts and authorization queries
*/
@SuppressWarnings({&quot;UnusedDeclaration&quot;})
public void setContextFactory(LdapContextFactory contextFactory) {
<span class="fc" id="L263"> this.contextFactory = contextFactory;</span>
<span class="fc" id="L264"> }</span>
/**
* Returns the LdapContextFactory instance used to acquire connections to the LDAP directory during authentication
* attempts and authorization queries. Unless specified otherwise, the default is a {@link JndiLdapContextFactory}
* instance.
*
* @return the LdapContextFactory instance used to acquire connections to the LDAP directory during
* authentication attempts and authorization queries
*/
public LdapContextFactory getContextFactory() {
<span class="fc" id="L275"> return this.contextFactory;</span>
}
/*--------------------------------------------
| M E T H O D S |
============================================*/
/**
* Delegates to {@link #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken, LdapContextFactory)},
* wrapping any {@link NamingException}s in a Shiro {@link AuthenticationException} to satisfy the parent method
* signature.
*
* @param token the authentication token containing the user's principal and credentials.
* @return the {@link AuthenticationInfo} acquired after a successful authentication attempt
* @throws AuthenticationException if the authentication attempt fails or if a
* {@link NamingException} occurs.
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
<span class="fc" id="L295"> info = queryForAuthenticationInfo(token, getContextFactory());</span>
<span class="nc" id="L296"> } catch (AuthenticationNotSupportedException e) {</span>
<span class="nc" id="L297"> String msg = &quot;Unsupported configured authentication mechanism&quot;;</span>
<span class="nc" id="L298"> throw new UnsupportedAuthenticationMechanismException(msg, e);</span>
<span class="fc" id="L299"> } catch (javax.naming.AuthenticationException e) {</span>
<span class="fc" id="L300"> throw new AuthenticationException(&quot;LDAP authentication failed.&quot;, e);</span>
<span class="fc" id="L301"> } catch (NamingException e) {</span>
<span class="fc" id="L302"> String msg = &quot;LDAP naming error while attempting to authenticate user.&quot;;</span>
<span class="fc" id="L303"> throw new AuthenticationException(msg, e);</span>
<span class="fc" id="L304"> }</span>
<span class="fc" id="L306"> return info;</span>
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
AuthorizationInfo info;
try {
<span class="nc" id="L313"> info = queryForAuthorizationInfo(principals, getContextFactory());</span>
<span class="nc" id="L314"> } catch (NamingException e) {</span>
<span class="nc" id="L315"> String msg = &quot;LDAP naming error while attempting to retrieve authorization for user [&quot; + principals + &quot;].&quot;;</span>
<span class="nc" id="L316"> throw new AuthorizationException(msg, e);</span>
<span class="nc" id="L317"> }</span>
<span class="nc" id="L319"> return info;</span>
}
/**
* Returns the principal to use when creating the LDAP connection for an authentication attempt.
* &lt;p/&gt;
* This implementation uses a heuristic: it checks to see if the specified token's
* {@link AuthenticationToken#getPrincipal() principal} is a {@code String}, and if so,
* {@link #getUserDn(String) converts it} from what is
* assumed to be a raw uid or username {@code String} into a User DN {@code String}. Almost all LDAP directories
* expect the authentication connection to present a User DN and not an unqualified username or uid.
* &lt;p/&gt;
* If the token's {@code principal} is not a String, it is assumed to already be in the format supported by the
* underlying {@link LdapContextFactory} implementation and the raw principal is returned directly.
*
* @param token the {@link AuthenticationToken} submitted during the authentication process
* @return the User DN or raw principal to use to acquire the LdapContext.
* @see LdapContextFactory#getLdapContext(Object, Object)
*/
protected Object getLdapPrincipal(AuthenticationToken token) {
<span class="fc" id="L339"> Object principal = token.getPrincipal();</span>
<span class="fc bfc" id="L340" title="All 2 branches covered."> if (principal instanceof String) {</span>
<span class="fc" id="L341"> String sPrincipal = (String) principal;</span>
<span class="fc" id="L342"> return getUserDn(sPrincipal);</span>
}
<span class="fc" id="L344"> return principal;</span>
}
/**
* This implementation opens an LDAP connection using the token's
* {@link #getLdapPrincipal(org.apache.shiro.authc.AuthenticationToken) discovered principal} and provided
* {@link AuthenticationToken#getCredentials() credentials}. If the connection opens successfully, the
* authentication attempt is immediately considered successful and a new
* {@link AuthenticationInfo} instance is
* {@link #createAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken, Object, Object, javax.naming.ldap.LdapContext) created}
* and returned. If the connection cannot be opened, either because LDAP authentication failed or some other
* JNDI problem, an {@link NamingException} will be thrown.
*
* @param token the submitted authentication token that triggered the authentication attempt.
* @param ldapContextFactory factory used to retrieve LDAP connections.
* @return an {@link AuthenticationInfo} instance representing the authenticated user's information.
* @throws NamingException if any LDAP errors occur.
*/
protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token,
LdapContextFactory ldapContextFactory)
throws NamingException {
<span class="fc" id="L366"> Object principal = token.getPrincipal();</span>
<span class="fc" id="L367"> Object credentials = token.getCredentials();</span>
<span class="fc" id="L369"> log.debug(&quot;Authenticating user '{}' through LDAP&quot;, principal);</span>
<span class="fc" id="L371"> principal = getLdapPrincipal(token);</span>
<span class="fc" id="L373"> LdapContext ctx = null;</span>
try {
<span class="fc" id="L375"> ctx = ldapContextFactory.getLdapContext(principal, credentials);</span>
//context was opened successfully, which means their credentials were valid. Return the AuthenticationInfo:
<span class="fc" id="L377"> return createAuthenticationInfo(token, principal, credentials, ctx);</span>
} finally {
<span class="fc" id="L379"> LdapUtils.closeContext(ctx);</span>
}
}
/**
* Returns the {@link AuthenticationInfo} resulting from a Subject's successful LDAP authentication attempt.
* &lt;p/&gt;
* This implementation ignores the {@code ldapPrincipal}, {@code ldapCredentials}, and the opened
* {@code ldapContext} arguments and merely returns an {@code AuthenticationInfo} instance mirroring the
* submitted token's principal and credentials. This is acceptable because this method is only ever invoked after
* a successful authentication attempt, which means the provided principal and credentials were correct, and can
* be used directly to populate the (now verified) {@code AuthenticationInfo}.
* &lt;p/&gt;
* Subclasses however are free to override this method for more advanced construction logic.
*
* @param token the submitted {@code AuthenticationToken} that resulted in a successful authentication
* @param ldapPrincipal the LDAP principal used when creating the LDAP connection. Unlike the token's
* {@link AuthenticationToken#getPrincipal() principal}, this value is usually a constructed
* User DN and not a simple username or uid. The exact value is depending on the
* configured
* &lt;a href=&quot;http://download-llnw.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html&quot;&gt;
* LDAP authentication mechanism&lt;/a&gt; in use.
* @param ldapCredentials the LDAP credentials used when creating the LDAP connection.
* @param ldapContext the LdapContext created that resulted in a successful authentication. It can be used
* further by subclasses for more complex operations. It does not need to be closed -
* it will be closed automatically after this method returns.
* @return the {@link AuthenticationInfo} resulting from a Subject's successful LDAP authentication attempt.
* @throws NamingException if there was any problem using the {@code LdapContext}
*/
@SuppressWarnings({&quot;UnusedDeclaration&quot;})
protected AuthenticationInfo createAuthenticationInfo(AuthenticationToken token, Object ldapPrincipal,
Object ldapCredentials, LdapContext ldapContext)
throws NamingException {
<span class="fc" id="L412"> return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());</span>
}
/**
* Method that should be implemented by subclasses to build an
* {@link AuthorizationInfo} object by querying the LDAP context for the
* specified principal.&lt;/p&gt;
*
* @param principals the principals of the Subject whose AuthenticationInfo should be queried from the LDAP server.
* @param ldapContextFactory factory used to retrieve LDAP connections.
* @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server.
* @throws NamingException if any LDAP errors occur during the search.
*/
protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals,
LdapContextFactory ldapContextFactory) throws NamingException {
<span class="nc" id="L428"> return null;</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.3.201901230119</span></div></body></html>