diff --git a/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java b/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java index ab0de580ae..f7ffd1d90f 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java +++ b/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package org.springframework.security.ldap; -import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -84,7 +83,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource { setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() { @Override - @SuppressWarnings("rawtypes") + @SuppressWarnings("unchecked") public void setupEnvironment(Hashtable env, String dn, String password) { super.setupEnvironment(env, dn, password); // Remove the pooling flag unless authenticating as the 'manager' user. @@ -145,7 +144,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource { StringBuilder providerUrl = new StringBuilder(); for (String serverUrl : urls) { String trimmedUrl = serverUrl.trim(); - if ("".equals(trimmedUrl)) { + if (trimmedUrl.isEmpty()) { continue; } providerUrl.append(trimmedUrl); @@ -160,21 +159,11 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource { } private static String encodeUrl(String url) { - try { - return URLEncoder.encode(url, StandardCharsets.UTF_8.toString()); - } - catch (UnsupportedEncodingException ex) { - throw new IllegalStateException(ex); - } + return URLEncoder.encode(url, StandardCharsets.UTF_8); } private String decodeUrl(String url) { - try { - return URLDecoder.decode(url, StandardCharsets.UTF_8.toString()); - } - catch (UnsupportedEncodingException ex) { - throw new IllegalStateException(ex); - } + return URLDecoder.decode(url, StandardCharsets.UTF_8); } } diff --git a/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java b/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java index a3911aa180..1f1fb6cc0f 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java +++ b/ldap/src/main/java/org/springframework/security/ldap/LdapEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2010 the original author or authors. + * Copyright 2005-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.security.ldap; -import org.springframework.ldap.BadLdapGrammarException; - /** * Helper class to encode and decode ldap names and values. * @@ -31,26 +29,7 @@ import org.springframework.ldap.BadLdapGrammarException; */ final class LdapEncoder { - private static final int HEX = 16; - - private static String[] NAME_ESCAPE_TABLE = new String[96]; - static { - // all below 0x20 (control chars) - for (char c = 0; c < ' '; c++) { - NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c); - } - NAME_ESCAPE_TABLE['#'] = "\\#"; - NAME_ESCAPE_TABLE[','] = "\\,"; - NAME_ESCAPE_TABLE[';'] = "\\;"; - NAME_ESCAPE_TABLE['='] = "\\="; - NAME_ESCAPE_TABLE['+'] = "\\+"; - NAME_ESCAPE_TABLE['<'] = "\\<"; - NAME_ESCAPE_TABLE['>'] = "\\>"; - NAME_ESCAPE_TABLE['\"'] = "\\\""; - NAME_ESCAPE_TABLE['\\'] = "\\\\"; - } - - private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1]; + private static final String[] FILTER_ESCAPE_TABLE = new String['\\' + 1]; static { // fill with char itself @@ -71,11 +50,6 @@ final class LdapEncoder { private LdapEncoder() { } - protected static String toTwoCharHex(char c) { - String raw = Integer.toHexString(c).toUpperCase(); - return (raw.length() > 1) ? raw : "0" + raw; - } - /** * Escape a value for use in a filter. * @param value the value to escape. @@ -94,102 +68,4 @@ final class LdapEncoder { return encodedValue.toString(); } - /** - * LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI! - * - *
- * Escapes:
- * ' ' [space] - "\ " [if first or last]
- * '#' [hash] - "\#"
- * ',' [comma] - "\,"
- * ';' [semicolon] - "\;"
- * '= [equals] - "\="
- * '+' [plus] - "\+"
- * '<' [less than] - "\<"
- * '>' [greater than] - "\>"
- * '"' [double quote] - "\""
- * '\' [backslash] - "\\"
- * @param value the value to escape. - * @return The escaped value. - */ - static String nameEncode(String value) { - if (value == null) { - return null; - } - StringBuilder encodedValue = new StringBuilder(value.length() * 2); - int length = value.length(); - int last = length - 1; - for (int i = 0; i < length; i++) { - char c = value.charAt(i); - // space first or last - if (c == ' ' && (i == 0 || i == last)) { - encodedValue.append("\\ "); - continue; - } - // check in table for escapes - if (c < NAME_ESCAPE_TABLE.length) { - String esc = NAME_ESCAPE_TABLE[c]; - if (esc != null) { - encodedValue.append(esc); - continue; - } - } - // default: add the char - encodedValue.append(c); - } - return encodedValue.toString(); - } - - /** - * Decodes a value. Converts escaped chars to ordinary chars. - * @param value Trimmed value, so no leading an trailing blanks, except an escaped - * space last. - * @return The decoded value as a string. - * @throws BadLdapGrammarException - */ - static String nameDecode(String value) throws BadLdapGrammarException { - if (value == null) { - return null; - } - StringBuilder decoded = new StringBuilder(value.length()); - int i = 0; - while (i < value.length()) { - char currentChar = value.charAt(i); - if (currentChar == '\\') { - // Ending with a single backslash is not allowed - if (value.length() <= i + 1) { - throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'"); - } - char nextChar = value.charAt(i + 1); - if (isNormalBackslashEscape(nextChar)) { - decoded.append(nextChar); - i += 2; - } - else { - if (value.length() <= i + 2) { - throw new BadLdapGrammarException( - "Unexpected end of value " + "expected special or hex, found '" + nextChar + "'"); - } - // This should be a hex value - String hexString = "" + nextChar + value.charAt(i + 2); - decoded.append((char) Integer.parseInt(hexString, HEX)); - i += 3; - } - } - else { - // This character wasn't escaped - just append it - decoded.append(currentChar); - i++; - } - } - - return decoded.toString(); - - } - - private static boolean isNormalBackslashEscape(char nextChar) { - return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>' - || nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' '; - } - } diff --git a/ldap/src/main/java/org/springframework/security/ldap/LdapUtils.java b/ldap/src/main/java/org/springframework/security/ldap/LdapUtils.java index a3b2522bfb..a37ccb3074 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/LdapUtils.java +++ b/ldap/src/main/java/org/springframework/security/ldap/LdapUtils.java @@ -83,7 +83,7 @@ public final class LdapUtils { */ public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException { String baseDn = baseCtx.getNameInNamespace(); - if (baseDn.length() == 0) { + if (baseDn.isEmpty()) { return fullDn; } LdapName base = LdapNameBuilder.newInstance(baseDn).build(); diff --git a/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java b/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java index 0c3056e563..2b26442ab7 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java +++ b/ldap/src/main/java/org/springframework/security/ldap/SpringSecurityLdapTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ package org.springframework.security.ldap; import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -40,7 +40,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.dao.IncorrectResultSizeDataAccessException; -import org.springframework.ldap.core.ContextExecutor; import org.springframework.ldap.core.ContextMapper; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.DirContextAdapter; @@ -98,7 +97,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { searchControls.setSearchScope(SearchControls.OBJECT_SCOPE); Object[] params = new Object[] { value }; NamingEnumeration results = ctx.search(dn, comparisonFilter, params, searchControls); - Boolean match = results.hasMore(); + boolean match = results.hasMore(); LdapUtils.closeEnumeration(results); return match; }); @@ -112,7 +111,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { * @return the object created by the mapper */ public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) { - return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> { + return executeReadOnly((ctx) -> { Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve); return new DirContextAdapter(attrs, LdapNameBuilder.newInstance(dn).build(), LdapNameBuilder.newInstance(ctx.getNameInNamespace()).build()); @@ -169,18 +168,19 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { String formattedFilter = MessageFormat.format(filter, encodedParams); logger.trace(LogMessage.format("Using filter: %s", formattedFilter)); HashSet>> result = new HashSet<>(); - ContextMapper roleMapper = (ctx) -> { + ContextMapper roleMapper = (ctx) -> { DirContextAdapter adapter = (DirContextAdapter) ctx; Map> record = new HashMap<>(); if (ObjectUtils.isEmpty(attributeNames)) { try { - for (NamingEnumeration enumeration = adapter.getAttributes().getAll(); enumeration.hasMore();) { - Attribute attr = (Attribute) enumeration.next(); + for (NamingEnumeration enumeration = adapter.getAttributes() + .getAll(); enumeration.hasMore();) { + Attribute attr = enumeration.next(); extractStringAttributeValues(adapter, record, attr.getID()); } } catch (NamingException ex) { - org.springframework.ldap.support.LdapUtils.convertLdapException(ex); + throw org.springframework.ldap.support.LdapUtils.convertLdapException(ex); } } else { @@ -188,7 +188,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { extractStringAttributeValues(adapter, record, attributeName); } } - record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter))); + record.put(DN_KEY, Collections.singletonList(getAdapterDN(adapter))); result.add(record); return null; }; @@ -258,8 +258,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { * search returns more than one result. */ public DirContextOperations searchForSingleEntry(String base, String filter, Object[] params) { - return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> searchForSingleEntryInternal(ctx, - this.searchControls, base, filter, params)); + return executeReadOnly((ctx) -> searchForSingleEntryInternal(ctx, this.searchControls, base, filter, params)); } /** @@ -296,8 +295,9 @@ public class SpringSecurityLdapTemplate extends LdapTemplate { /** * We need to make sure the search controls has the return object flag set to true, in * order for the search to return DirContextAdapter instances. - * @param originalControls - * @return + * @param originalControls the {@link SearchControls} that might have the return + * object flag set to true + * @return a {@link SearchControls} that does have the return object flag set to true */ private static SearchControls buildControls(SearchControls originalControls) { return new SearchControls(originalControls.getSearchScope(), originalControls.getCountLimit(), diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java index 5263f9cecb..f44d296197 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.lang.NonNull; import org.springframework.ldap.core.DirContextOperations; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; @@ -117,14 +118,15 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati * obtained from the UserDetails object created by the configured * {@code UserDetailsContextMapper}. Often it will not be possible to read the * password from the directory, so defaults to true. - * @param useAuthenticationRequestCredentials + * @param useAuthenticationRequestCredentials whether to use the credentials in the + * authentication request */ public void setUseAuthenticationRequestCredentials(boolean useAuthenticationRequestCredentials) { this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials; } @Override - public void setMessageSource(MessageSource messageSource) { + public void setMessageSource(@NonNull MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticator.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticator.java index 346aa1ca4d..4b33cb3b9f 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticator.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticator.java @@ -25,6 +25,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.lang.NonNull; import org.springframework.ldap.core.ContextSource; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.ldap.search.LdapUserSearch; @@ -37,6 +38,8 @@ import org.springframework.util.Assert; */ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, InitializingBean, MessageSourceAware { + private final Object mutex = new Object(); + private final ContextSource contextSource; /** @@ -59,7 +62,7 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In /** * Create an initialized instance with the {@link ContextSource} provided. - * @param contextSource + * @param contextSource the {@link ContextSource} to use */ public AbstractLdapAuthenticator(ContextSource contextSource) { Assert.notNull(contextSource, "contextSource must not be null."); @@ -93,7 +96,7 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In } List userDns = new ArrayList<>(this.userDnFormat.length); String[] args = new String[] { LdapEncoder.nameEncode(username) }; - synchronized (this.userDnFormat) { + synchronized (this.mutex) { for (MessageFormat formatter : this.userDnFormat) { userDns.add(formatter.format(args)); } @@ -106,14 +109,14 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In } @Override - public void setMessageSource(MessageSource messageSource) { + public void setMessageSource(@NonNull MessageSource messageSource) { Assert.notNull(messageSource, "Message source must not be null"); this.messages = new MessageSourceAccessor(messageSource); } /** * Sets the user attributes which will be retrieved from the directory. - * @param userAttributes + * @param userAttributes the set of user attributes to retrieve */ public void setUserAttributes(String[] userAttributes) { Assert.notNull(userAttributes, "The userAttributes property cannot be set to null"); diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticator.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticator.java index f1492aa34b..c45bb1308e 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticator.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticator.java @@ -33,7 +33,7 @@ public interface LdapAuthenticator { /** * Authenticates as a user and obtains additional user information from the directory. - * @param authentication + * @param authentication the authentication request * @return the details of the successfully authenticated user. */ DirContextOperations authenticate(Authentication authentication); diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java index f79f4843ae..0a5f29061c 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2010 the original author or authors. + * Copyright 2005-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.security.ldap.authentication; -import org.springframework.ldap.BadLdapGrammarException; - /** * Helper class to encode and decode ldap names and values. * @@ -31,9 +29,7 @@ import org.springframework.ldap.BadLdapGrammarException; */ final class LdapEncoder { - private static final int HEX = 16; - - private static String[] NAME_ESCAPE_TABLE = new String[96]; + private static final String[] NAME_ESCAPE_TABLE = new String[96]; static { // all below 0x20 (control chars) for (char c = 0; c < ' '; c++) { @@ -50,54 +46,19 @@ final class LdapEncoder { NAME_ESCAPE_TABLE['\\'] = "\\\\"; } - private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1]; - - static { - // fill with char itself - for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) { - FILTER_ESCAPE_TABLE[c] = String.valueOf(c); - } - // escapes (RFC2254) - FILTER_ESCAPE_TABLE['*'] = "\\2a"; - FILTER_ESCAPE_TABLE['('] = "\\28"; - FILTER_ESCAPE_TABLE[')'] = "\\29"; - FILTER_ESCAPE_TABLE['\\'] = "\\5c"; - FILTER_ESCAPE_TABLE[0] = "\\00"; - } - /** * All static methods - not to be instantiated. */ private LdapEncoder() { } - protected static String toTwoCharHex(char c) { + static String toTwoCharHex(char c) { String raw = Integer.toHexString(c).toUpperCase(); return (raw.length() > 1) ? raw : "0" + raw; } /** - * Escape a value for use in a filter. - * @param value the value to escape. - * @return a properly escaped representation of the supplied value. - */ - static String filterEncode(String value) { - if (value == null) { - return null; - } - StringBuilder encodedValue = new StringBuilder(value.length() * 2); - int length = value.length(); - for (int i = 0; i < length; i++) { - char ch = value.charAt(i); - encodedValue.append((ch < FILTER_ESCAPE_TABLE.length) ? FILTER_ESCAPE_TABLE[ch] : ch); - } - return encodedValue.toString(); - } - - /** - * LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI! - * - *
+ * LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
* Escapes:
* ' ' [space] - "\ " [if first or last]
* '#' [hash] - "\#"
@@ -140,56 +101,4 @@ final class LdapEncoder { return encodedValue.toString(); } - /** - * Decodes a value. Converts escaped chars to ordinary chars. - * @param value Trimmed value, so no leading an trailing blanks, except an escaped - * space last. - * @return The decoded value as a string. - * @throws BadLdapGrammarException - */ - static String nameDecode(String value) throws BadLdapGrammarException { - if (value == null) { - return null; - } - StringBuilder decoded = new StringBuilder(value.length()); - int i = 0; - while (i < value.length()) { - char currentChar = value.charAt(i); - if (currentChar == '\\') { - // Ending with a single backslash is not allowed - if (value.length() <= i + 1) { - throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'"); - } - char nextChar = value.charAt(i + 1); - if (isNormalBackslashEscape(nextChar)) { - decoded.append(nextChar); - i += 2; - } - else { - if (value.length() <= i + 2) { - throw new BadLdapGrammarException( - "Unexpected end of value " + "expected special or hex, found '" + nextChar + "'"); - } - // This should be a hex value - String hexString = "" + nextChar + value.charAt(i + 2); - decoded.append((char) Integer.parseInt(hexString, HEX)); - i += 3; - } - } - else { - // This character wasn't escaped - just append it - decoded.append(currentChar); - i++; - } - } - - return decoded.toString(); - - } - - private static boolean isNormalBackslashEscape(char nextChar) { - return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>' - || nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' '; - } - } diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/SpringSecurityAuthenticationSource.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/SpringSecurityAuthenticationSource.java index 035fb27e3b..7afa7c4369 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/SpringSecurityAuthenticationSource.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/SpringSecurityAuthenticationSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,8 +57,7 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource return ""; } Object principal = authentication.getPrincipal(); - if (principal instanceof LdapUserDetails) { - LdapUserDetails details = (LdapUserDetails) principal; + if (principal instanceof LdapUserDetails details) { return details.getDn(); } if (authentication instanceof AnonymousAuthenticationToken) { diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryAuthenticationException.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryAuthenticationException.java index f4d5199b23..42b0403740 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryAuthenticationException.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryAuthenticationException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,6 @@ import org.springframework.security.core.AuthenticationException; * * @author Rob Winch */ -@SuppressWarnings("serial") public final class ActiveDirectoryAuthenticationException extends AuthenticationException { private final String dataCode; diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java index c34f17f1a8..2096228f7a 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java @@ -189,12 +189,11 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda private DirContext bindAsUser(String username, String password) { // TODO. add DNS lookup based on domain - final String bindUrl = this.url; Hashtable env = new Hashtable<>(); env.put(Context.SECURITY_AUTHENTICATION, "simple"); String bindPrincipal = createBindPrincipal(username); env.put(Context.SECURITY_PRINCIPAL, bindPrincipal); - env.put(Context.PROVIDER_URL, bindUrl); + env.put(Context.PROVIDER_URL, this.url); env.put(Context.SECURITY_CREDENTIALS, password); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName()); diff --git a/ldap/src/main/java/org/springframework/security/ldap/jackson2/InetOrgPersonMixin.java b/ldap/src/main/java/org/springframework/security/ldap/jackson2/InetOrgPersonMixin.java index fca449114e..21ff071976 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/jackson2/InetOrgPersonMixin.java +++ b/ldap/src/main/java/org/springframework/security/ldap/jackson2/InetOrgPersonMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * Copyright 2015-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ import org.springframework.security.ldap.userdetails.InetOrgPerson; * @see LdapJackson2Module * @see SecurityJackson2Modules */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapAuthorityMixin.java b/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapAuthorityMixin.java index 85fe16f5fd..7494687aba 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapAuthorityMixin.java +++ b/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapAuthorityMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * Copyright 2015-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ import org.springframework.security.ldap.userdetails.LdapAuthority; * @see LdapJackson2Module * @see SecurityJackson2Modules */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE) @JsonIgnoreProperties(ignoreUnknown = true) abstract class LdapAuthorityMixin { diff --git a/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapJackson2Module.java b/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapJackson2Module.java index 62cb17a11a..f84e8df620 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapJackson2Module.java +++ b/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapJackson2Module.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * Copyright 2015-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import org.springframework.security.ldap.userdetails.Person; * {@link LdapAuthorityMixin}, {@link LdapUserDetailsImplMixin}, {@link PersonMixin}, * {@link InetOrgPersonMixin}. * + *

* If not already enabled, default typing will be automatically enabled as type info is * required to properly serialize/deserialize objects. In order to use this module just * add it to your {@code ObjectMapper} configuration. diff --git a/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapUserDetailsImplMixin.java b/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapUserDetailsImplMixin.java index a441102e6b..ef6e470b60 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapUserDetailsImplMixin.java +++ b/ldap/src/main/java/org/springframework/security/ldap/jackson2/LdapUserDetailsImplMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * Copyright 2015-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl; * @see LdapJackson2Module * @see SecurityJackson2Modules */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/ldap/src/main/java/org/springframework/security/ldap/jackson2/PersonMixin.java b/ldap/src/main/java/org/springframework/security/ldap/jackson2/PersonMixin.java index a3a0ddebc5..2063186658 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/jackson2/PersonMixin.java +++ b/ldap/src/main/java/org/springframework/security/ldap/jackson2/PersonMixin.java @@ -1,5 +1,5 @@ /* - * Copyright 2015-2021 the original author or authors. + * Copyright 2015-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ import org.springframework.security.ldap.userdetails.Person; * @see LdapJackson2Module * @see SecurityJackson2Modules */ -@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY) +@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE) @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyAwareContextSource.java b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyAwareContextSource.java index 9c4ee19ecf..043b7cc1ec 100755 --- a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyAwareContextSource.java +++ b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyAwareContextSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,7 +77,7 @@ public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityConte @Override @SuppressWarnings("unchecked") protected Hashtable getAuthenticatedEnv(String principal, String credentials) { - Hashtable env = super.getAuthenticatedEnv(principal, credentials); + Hashtable env = super.getAuthenticatedEnv(principal, credentials); env.put(LdapContext.CONTROL_FACTORIES, PasswordPolicyControlFactory.class.getName()); return env; } diff --git a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyResponseControl.java b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyResponseControl.java index f0b89483d7..2aa2b330e0 100755 --- a/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyResponseControl.java +++ b/ldap/src/main/java/org/springframework/security/ldap/ppolicy/PasswordPolicyResponseControl.java @@ -220,7 +220,7 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl { } } - class SpecificTagDecoder extends BERTagDecoder { + static class SpecificTagDecoder extends BERTagDecoder { /** Allows us to remember which of the two options we're decoding */ private Boolean inChoice = null; diff --git a/ldap/src/main/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearch.java b/ldap/src/main/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearch.java index d3f92397c9..326b5c5fa1 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearch.java +++ b/ldap/src/main/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearch.java @@ -52,7 +52,7 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch { /** * Context name to search in, relative to the base of the configured ContextSource. */ - private String searchBase = ""; + private final String searchBase; /** * The filter expression used in the user search. This is an LDAP search filter (as @@ -78,9 +78,9 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch { this.contextSource = contextSource; this.searchBase = searchBase; setSearchSubtree(true); - if (searchBase.length() == 0) { + if (searchBase.isEmpty()) { logger.info(LogMessage.format("Searches will be performed from the root %s since SearchBase not set", - contextSource.getBaseLdapPath())); + contextSource.getBaseLdapName())); } } diff --git a/ldap/src/main/java/org/springframework/security/ldap/server/UnboundIdContainer.java b/ldap/src/main/java/org/springframework/security/ldap/server/UnboundIdContainer.java index c93fa15b8c..ee9f6254f9 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/server/UnboundIdContainer.java +++ b/ldap/src/main/java/org/springframework/security/ldap/server/UnboundIdContainer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.Lifecycle; import org.springframework.core.io.Resource; +import org.springframework.lang.NonNull; import org.springframework.util.StringUtils; /** @@ -43,7 +44,7 @@ public class UnboundIdContainer private InMemoryDirectoryServer directoryServer; - private String defaultPartitionSuffix; + private final String defaultPartitionSuffix; private int port = 53389; @@ -51,7 +52,7 @@ public class UnboundIdContainer private boolean running; - private String ldif; + private final String ldif; public UnboundIdContainer(String defaultPartitionSuffix, String ldif) { this.defaultPartitionSuffix = defaultPartitionSuffix; @@ -79,7 +80,7 @@ public class UnboundIdContainer } @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { this.context = applicationContext; } diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java index b69985d2b4..0a1bc4035d 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/DefaultLdapAuthoritiesPopulator.java @@ -129,7 +129,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator /** * The base DN from which the search for group membership should be performed */ - private String groupSearchBase; + private final String groupSearchBase; /** * The pattern to be used for the user search. {0} is the user's DN @@ -166,7 +166,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator if (groupSearchBase == null) { logger.info("Will not perform group search since groupSearchBase is null."); } - else if (groupSearchBase.length() == 0) { + else if (groupSearchBase.isEmpty()) { logger.info("Will perform group search from the context source base since groupSearchBase is empty."); } this.authorityMapper = (record) -> { @@ -365,16 +365,6 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator return this.convertToUpperCase; } - /** - * Returns the default role Method available so that classes extending this can - * override - * @return the default role used - * @see #setDefaultRole(String) - */ - private GrantedAuthority getDefaultRole() { - return this.defaultRole; - } - /** * Returns the search controls Method available so that classes extending this can * override the search controls used diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapAuthority.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapAuthority.java index e08678b2a4..424fe11cc4 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapAuthority.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapAuthority.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,16 +31,16 @@ import org.springframework.util.Assert; */ public class LdapAuthority implements GrantedAuthority { - private String dn; + private final String dn; - private String role; + private final String role; - private Map> attributes; + private final Map> attributes; /** * Constructs an LdapAuthority that has a role and a DN but no other attributes - * @param role - * @param dn + * @param role the principal's role + * @param dn the distinguished name */ public LdapAuthority(String role, String dn) { this(role, dn, null); @@ -48,9 +48,9 @@ public class LdapAuthority implements GrantedAuthority { /** * Constructs an LdapAuthority with the given role, DN and other LDAP attributes - * @param role - * @param dn - * @param attributes + * @param role the principal's role + * @param dn the distinguished name + * @param attributes additional LDAP attributes */ public LdapAuthority(String role, String dn, Map> attributes) { Assert.notNull(role, "role can not be null"); @@ -70,7 +70,7 @@ public class LdapAuthority implements GrantedAuthority { /** * Returns the DN for this LDAP authority - * @return + * @return the distinguished name */ public String getDn() { return this.dn; @@ -91,7 +91,7 @@ public class LdapAuthority implements GrantedAuthority { /** * Returns the first attribute value for a specified attribute - * @param name + * @param name the attribute name * @return the first attribute value for a specified attribute, may be null */ public String getFirstAttributeValue(String name) { diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java index 3ace03ebe8..8db6ec99da 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/LdapUserDetailsManager.java @@ -44,7 +44,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.log.LogMessage; import org.springframework.ldap.core.AttributesMapper; import org.springframework.ldap.core.AttributesMapperCallbackHandler; -import org.springframework.ldap.core.ContextExecutor; import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DistinguishedName; @@ -121,7 +120,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { private final LdapTemplate template; /** Default context mapper used to create a set of roles from a list of attributes */ - private AttributesMapper roleMapper = (attributes) -> { + private AttributesMapper roleMapper = (attributes) -> { Attribute roleAttr = attributes.get(this.groupRoleAttributeName); NamingEnumeration ne = roleAttr.getAll(); Object group = ne.next(); @@ -147,7 +146,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { } private DirContextAdapter loadUserAsContext(final LdapName dn, final String username) { - return (DirContextAdapter) this.template.executeReadOnly((ContextExecutor) (ctx) -> { + return this.template.executeReadOnly((ctx) -> { try { Attributes attrs = ctx.getAttributes(dn, this.attributesToRetrieve); return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx)); @@ -162,6 +161,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { * Changes the password for the current user. The username is obtained from the * security context. * + *

* There are two supported strategies for modifying the user's password depending on * the capabilities of the corresponding LDAP server. * @@ -170,6 +170,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { * LDAP Password Modify * Extended Operation . * + *

* See {@link LdapUserDetailsManager#setUsePasswordModifyExtensionOperation(boolean)} * for details. *

@@ -205,7 +206,6 @@ public class LdapUserDetailsManager implements UserDetailsManager { * @param username the user whose roles are required. * @return the granted authorities returned by the group search */ - @SuppressWarnings("unchecked") List getUserAuthorities(final LdapName dn, final String username) { SearchExecutor se = (ctx) -> { LdapName fullDn = LdapUtils.getFullDn(dn, ctx); @@ -214,7 +214,8 @@ public class LdapUserDetailsManager implements UserDetailsManager { return ctx.search(this.groupSearchBase, this.groupSearchFilter, new String[] { fullDn.toString(), username }, ctrls); }; - AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler(this.roleMapper); + AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler<>( + this.roleMapper); this.template.search(se, roleCollector); return roleCollector.getList(); } @@ -229,7 +230,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { // Check for any existing authorities which might be set for this // DN and remove them List authorities = getUserAuthorities(dn, user.getUsername()); - if (authorities.size() > 0) { + if (!authorities.isEmpty()) { removeAuthorities(dn, authorities); } addAuthorities(dn, user.getAuthorities()); @@ -322,7 +323,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { private void modifyAuthorities(final LdapName userDn, final Collection authorities, final int modType) { - this.template.executeReadWrite((ContextExecutor) (ctx) -> { + this.template.executeReadWrite((ctx) -> { for (GrantedAuthority authority : authorities) { String group = convertAuthorityToGroup(authority); LdapName fullDn = LdapUtils.getFullDn(userDn, ctx); @@ -389,20 +390,26 @@ public class LdapUserDetailsManager implements UserDetailsManager { /** * Sets the method by which a user's password gets modified. * + *

* If set to {@code true}, then {@link LdapUserDetailsManager#changePassword} will * modify the user's password by way of the * Password Modify * Extension Operation. * + *

* If set to {@code false}, then {@link LdapUserDetailsManager#changePassword} will * modify the user's password by directly modifying attributes on the corresponding * entry. * + *

* Before using this setting, ensure that the corresponding LDAP server supports this * extended operation. * + *

* By default, {@code usePasswordModifyExtensionOperation} is false. - * @param usePasswordModifyExtensionOperation + * @param usePasswordModifyExtensionOperation whether to use the + * Password Modify + * Extension Operation to modify the password * @since 4.2.9 */ public void setUsePasswordModifyExtensionOperation(boolean usePasswordModifyExtensionOperation) { @@ -473,6 +480,7 @@ public class LdapUserDetailsManager implements UserDetailsManager { * LDAP Password Modify * Extended Operation client request. * + *

* Can be directed at any LDAP server that supports the Password Modify Extended * Operation. * diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulator.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulator.java index b61068ec8f..a957209a7d 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulator.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/NestedLdapAuthoritiesPopulator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -176,7 +176,7 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula if (getAttributeNames() == null) { setAttributeNames(new HashSet<>()); } - if (StringUtils.hasText(getGroupRoleAttribute()) && !getAttributeNames().contains(getGroupRoleAttribute())) { + if (StringUtils.hasText(getGroupRoleAttribute())) { getAttributeNames().add(getGroupRoleAttribute()); } Set>> userRoles = getLdapTemplate().searchForMultipleAttributeValues( @@ -200,7 +200,7 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula // this prevents a forever loop for a misconfigured ldap directory circular = circular | (!authorities.add(new LdapAuthority(role, dn, record))); } - String roleName = (roles.size() > 0) ? roles.iterator().next() : dn; + String roleName = (!roles.isEmpty()) ? roles.iterator().next() : dn; if (!circular) { performNestedSearch(dn, roleName, authorities, (depth - 1)); } diff --git a/ldap/src/main/java/org/springframework/security/ldap/userdetails/UserDetailsContextMapper.java b/ldap/src/main/java/org/springframework/security/ldap/userdetails/UserDetailsContextMapper.java index 9c4a9e7294..5ec499a07b 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/userdetails/UserDetailsContextMapper.java +++ b/ldap/src/main/java/org/springframework/security/ldap/userdetails/UserDetailsContextMapper.java @@ -39,7 +39,7 @@ public interface UserDetailsContextMapper { * Creates a fully populated UserDetails object for use by the security framework. * @param ctx the context object which contains the user information. * @param username the user's supplied login name. - * @param authorities + * @param authorities the authorities to add to the {@code UserDetails} instance * @return the user object. */ UserDetails mapUserFromContext(DirContextOperations ctx, String username,