Polish spring-security-ldap main code

Manually polish `spring-security-ldap` following the formatting
and checkstyle fixes.

Issue gh-8945
This commit is contained in:
Phillip Webb 2020-07-31 12:05:12 -07:00 committed by Rob Winch
parent 48957b42fe
commit 554ef627fb
33 changed files with 241 additions and 777 deletions

View File

@ -47,9 +47,7 @@ public class DefaultLdapUsernameToDnMapper implements LdapUsernameToDnMapper {
@Override @Override
public DistinguishedName buildDn(String username) { public DistinguishedName buildDn(String username) {
DistinguishedName dn = new DistinguishedName(this.userDnBase); DistinguishedName dn = new DistinguishedName(this.userDnBase);
dn.add(this.usernameAttribute, username); dn.add(this.usernameAttribute, username);
return dn; return dn;
} }

View File

@ -51,8 +51,6 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
private String rootDn;
/** /**
* Create and initialize an instance which will connect to the supplied LDAP URL. If * Create and initialize an instance which will connect to the supplied LDAP URL. If
* you want to use more than one server for fail-over, rather use the * you want to use more than one server for fail-over, rather use the
@ -62,44 +60,36 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
*/ */
public DefaultSpringSecurityContextSource(String providerUrl) { public DefaultSpringSecurityContextSource(String providerUrl) {
Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied."); Assert.hasLength(providerUrl, "An LDAP connection URL must be supplied.");
StringTokenizer tokenizer = new StringTokenizer(providerUrl);
StringTokenizer st = new StringTokenizer(providerUrl);
ArrayList<String> urls = new ArrayList<>(); ArrayList<String> urls = new ArrayList<>();
// Work out rootDn from the first URL and check that the other URLs (if any) match // Work out rootDn from the first URL and check that the other URLs (if any) match
while (st.hasMoreTokens()) { String rootDn = null;
String url = st.nextToken(); while (tokenizer.hasMoreTokens()) {
String url = tokenizer.nextToken();
String urlRootDn = LdapUtils.parseRootDnFromUrl(url); String urlRootDn = LdapUtils.parseRootDnFromUrl(url);
urls.add(url.substring(0, url.lastIndexOf(urlRootDn))); urls.add(url.substring(0, url.lastIndexOf(urlRootDn)));
this.logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'"); this.logger.info(" URL '" + url + "', root DN is '" + urlRootDn + "'");
Assert.isTrue(rootDn == null || rootDn.equals(urlRootDn),
if (this.rootDn == null) { "Root DNs must be the same when using multiple URLs");
this.rootDn = urlRootDn; rootDn = (rootDn != null) ? rootDn : urlRootDn;
}
else if (!this.rootDn.equals(urlRootDn)) {
throw new IllegalArgumentException("Root DNs must be the same when using multiple URLs");
}
} }
setUrls(urls.toArray(new String[0])); setUrls(urls.toArray(new String[0]));
setBase(this.rootDn); setBase(rootDn);
setPooled(true); setPooled(true);
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() { setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
@Override @Override
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public void setupEnvironment(Hashtable env, String dn, String password) { public void setupEnvironment(Hashtable env, String dn, String password) {
super.setupEnvironment(env, dn, password); super.setupEnvironment(env, dn, password);
// Remove the pooling flag unless we are authenticating as the 'manager' // Remove the pooling flag unless authenticating as the 'manager' user.
// user.
if (!DefaultSpringSecurityContextSource.this.userDn.equals(dn) if (!DefaultSpringSecurityContextSource.this.userDn.equals(dn)
&& env.containsKey(SUN_LDAP_POOLING_FLAG)) { && env.containsKey(SUN_LDAP_POOLING_FLAG)) {
DefaultSpringSecurityContextSource.this.logger.debug("Removing pooling flag for user " + dn); DefaultSpringSecurityContextSource.this.logger.debug("Removing pooling flag for user " + dn);
env.remove(SUN_LDAP_POOLING_FLAG); env.remove(SUN_LDAP_POOLING_FLAG);
} }
} }
}); });
} }
@ -146,16 +136,13 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
private static String buildProviderUrl(List<String> urls, String baseDn) { private static String buildProviderUrl(List<String> urls, String baseDn) {
Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null."); Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null.");
Assert.notEmpty(urls, "At least one LDAP server URL must be provided."); Assert.notEmpty(urls, "At least one LDAP server URL must be provided.");
String trimmedBaseDn = baseDn.trim(); String trimmedBaseDn = baseDn.trim();
StringBuilder providerUrl = new StringBuilder(); StringBuilder providerUrl = new StringBuilder();
for (String serverUrl : urls) { for (String serverUrl : urls) {
String trimmedUrl = serverUrl.trim(); String trimmedUrl = serverUrl.trim();
if ("".equals(trimmedUrl)) { if ("".equals(trimmedUrl)) {
continue; continue;
} }
providerUrl.append(trimmedUrl); providerUrl.append(trimmedUrl);
if (!trimmedUrl.endsWith("/")) { if (!trimmedUrl.endsWith("/")) {
providerUrl.append("/"); providerUrl.append("/");
@ -163,7 +150,6 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
providerUrl.append(trimmedBaseDn); providerUrl.append(trimmedBaseDn);
providerUrl.append(" "); providerUrl.append(" ");
} }
return providerUrl.toString(); return providerUrl.toString();
} }

View File

@ -34,18 +34,11 @@ final class LdapEncoder {
private static final int HEX = 16; private static final int HEX = 16;
private static String[] NAME_ESCAPE_TABLE = new String[96]; private static String[] NAME_ESCAPE_TABLE = new String[96];
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static { static {
// Name encoding table -------------------------------------
// all below 0x20 (control chars) // all below 0x20 (control chars)
for (char c = 0; c < ' '; c++) { for (char c = 0; c < ' '; c++) {
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(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[';'] = "\\;";
@ -55,21 +48,21 @@ final class LdapEncoder {
NAME_ESCAPE_TABLE['>'] = "\\>"; NAME_ESCAPE_TABLE['>'] = "\\>";
NAME_ESCAPE_TABLE['\"'] = "\\\""; NAME_ESCAPE_TABLE['\"'] = "\\\"";
NAME_ESCAPE_TABLE['\\'] = "\\\\"; NAME_ESCAPE_TABLE['\\'] = "\\\\";
}
// Filter encoding table ------------------------------------- private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static {
// fill with char itself // fill with char itself
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) { for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c); FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
} }
// escapes (RFC2254) // escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a"; FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28"; FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29"; FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c"; FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00"; FILTER_ESCAPE_TABLE[0] = "\\00";
} }
/** /**
@ -79,15 +72,8 @@ final class LdapEncoder {
} }
protected static String toTwoCharHex(char c) { protected static String toTwoCharHex(char c) {
String raw = Integer.toHexString(c).toUpperCase(); String raw = Integer.toHexString(c).toUpperCase();
return (raw.length() > 1) ? raw : "0" + raw;
if (raw.length() > 1) {
return raw;
}
else {
return "0" + raw;
}
} }
/** /**
@ -96,29 +82,15 @@ final class LdapEncoder {
* @return a properly escaped representation of the supplied value. * @return a properly escaped representation of the supplied value.
*/ */
static String filterEncode(String value) { static String filterEncode(String value) {
if (value == null) { if (value == null) {
return null; return null;
} }
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2); StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length(); int length = value.length();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
char ch = value.charAt(i);
char c = value.charAt(i); encodedValue.append((ch < FILTER_ESCAPE_TABLE.length) ? FILTER_ESCAPE_TABLE[ch] : ch);
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
}
else {
// default: add the char
encodedValue.append(c);
}
} }
return encodedValue.toString(); return encodedValue.toString();
} }
@ -141,43 +113,31 @@ final class LdapEncoder {
* @return The escaped value. * @return The escaped value.
*/ */
static String nameEncode(String value) { static String nameEncode(String value) {
if (value == null) { if (value == null) {
return null; return null;
} }
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2); StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length(); int length = value.length();
int last = length - 1; int last = length - 1;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
char c = value.charAt(i); char c = value.charAt(i);
// space first or last // space first or last
if (c == ' ' && (i == 0 || i == last)) { if (c == ' ' && (i == 0 || i == last)) {
encodedValue.append("\\ "); encodedValue.append("\\ ");
continue; continue;
} }
// check in table for escapes
if (c < NAME_ESCAPE_TABLE.length) { if (c < NAME_ESCAPE_TABLE.length) {
// check in table for escapes
String esc = NAME_ESCAPE_TABLE[c]; String esc = NAME_ESCAPE_TABLE[c];
if (esc != null) { if (esc != null) {
encodedValue.append(esc); encodedValue.append(esc);
continue; continue;
} }
} }
// default: add the char // default: add the char
encodedValue.append(c); encodedValue.append(c);
} }
return encodedValue.toString(); return encodedValue.toString();
} }
/** /**
@ -188,43 +148,32 @@ final class LdapEncoder {
* @throws BadLdapGrammarException * @throws BadLdapGrammarException
*/ */
static String nameDecode(String value) throws BadLdapGrammarException { static String nameDecode(String value) throws BadLdapGrammarException {
if (value == null) { if (value == null) {
return null; return null;
} }
// make buffer same size
StringBuilder decoded = new StringBuilder(value.length()); StringBuilder decoded = new StringBuilder(value.length());
int i = 0; int i = 0;
while (i < value.length()) { while (i < value.length()) {
char currentChar = value.charAt(i); char currentChar = value.charAt(i);
if (currentChar == '\\') { if (currentChar == '\\') {
// Ending with a single backslash is not allowed
if (value.length() <= i + 1) { if (value.length() <= i + 1) {
// Ending with a single backslash is not allowed
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'"); throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
} }
char nextChar = value.charAt(i + 1);
if (isNormalBackslashEscape(nextChar)) {
decoded.append(nextChar);
i += 2;
}
else { else {
char nextChar = value.charAt(i + 1); if (value.length() <= i + 2) {
if (nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>' throw new BadLdapGrammarException(
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' "Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
|| nextChar == ' ') {
// Normal backslash escape
decoded.append(nextChar);
i += 2;
}
else {
if (value.length() <= i + 2) {
throw new BadLdapGrammarException(
"Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
}
else {
// This should be a hex value
String hexString = "" + nextChar + value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString, HEX));
i += 3;
}
} }
// This should be a hex value
String hexString = "" + nextChar + value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString, HEX));
i += 3;
} }
} }
else { else {
@ -238,4 +187,9 @@ final class LdapEncoder {
} }
private static boolean isNormalBackslashEscape(char nextChar) {
return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' ';
}
} }

View File

@ -47,7 +47,6 @@ public final class LdapUtils {
if (ctx instanceof DirContextAdapter) { if (ctx instanceof DirContextAdapter) {
return; return;
} }
try { try {
if (ctx != null) { if (ctx != null) {
ctx.close(); ctx.close();
@ -81,24 +80,17 @@ public final class LdapUtils {
* @throws NamingException any exceptions thrown by the context are propagated. * @throws NamingException any exceptions thrown by the context are propagated.
*/ */
public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException { public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException {
String baseDn = baseCtx.getNameInNamespace(); String baseDn = baseCtx.getNameInNamespace();
if (baseDn.length() == 0) { if (baseDn.length() == 0) {
return fullDn; return fullDn;
} }
DistinguishedName base = new DistinguishedName(baseDn); DistinguishedName base = new DistinguishedName(baseDn);
DistinguishedName full = new DistinguishedName(fullDn); DistinguishedName full = new DistinguishedName(fullDn);
if (base.equals(full)) { if (base.equals(full)) {
return ""; return "";
} }
Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN"); Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");
full.removeFirst(base); full.removeFirst(base);
return full.toString(); return full.toString();
} }
@ -108,28 +100,22 @@ public final class LdapUtils {
*/ */
public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx) throws NamingException { public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx) throws NamingException {
DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace()); DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace());
if (dn.contains(baseDn)) { if (dn.contains(baseDn)) {
return dn; return dn;
} }
baseDn.append(dn); baseDn.append(dn);
return baseDn; return baseDn;
} }
public static String convertPasswordToString(Object passObj) { public static String convertPasswordToString(Object passObj) {
Assert.notNull(passObj, "Password object to convert must not be null"); Assert.notNull(passObj, "Password object to convert must not be null");
if (passObj instanceof byte[]) { if (passObj instanceof byte[]) {
return Utf8.decode((byte[]) passObj); return Utf8.decode((byte[]) passObj);
} }
else if (passObj instanceof String) { if (passObj instanceof String) {
return (String) passObj; return (String) passObj;
} }
else { throw new IllegalArgumentException("Password object was not a String or byte array.");
throw new IllegalArgumentException("Password object was not a String or byte array.");
}
} }
/** /**
@ -143,9 +129,7 @@ public final class LdapUtils {
*/ */
public static String parseRootDnFromUrl(String url) { public static String parseRootDnFromUrl(String url) {
Assert.hasLength(url, "url must have length"); Assert.hasLength(url, "url must have length");
String urlRootDn; String urlRootDn;
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) { if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
URI uri = parseLdapUrl(url); URI uri = parseLdapUrl(url);
urlRootDn = uri.getRawPath(); urlRootDn = uri.getRawPath();
@ -154,11 +138,9 @@ public final class LdapUtils {
// Assume it's an embedded server // Assume it's an embedded server
urlRootDn = url; urlRootDn = url;
} }
if (urlRootDn.startsWith("/")) { if (urlRootDn.startsWith("/")) {
urlRootDn = urlRootDn.substring(1); urlRootDn = urlRootDn.substring(1);
} }
return urlRootDn; return urlRootDn;
} }
@ -173,14 +155,11 @@ public final class LdapUtils {
private static URI parseLdapUrl(String url) { private static URI parseLdapUrl(String url) {
Assert.hasLength(url, "url must have length"); Assert.hasLength(url, "url must have length");
try { try {
return new URI(url); return new URI(url);
} }
catch (URISyntaxException ex) { catch (URISyntaxException ex) {
IllegalArgumentException iae = new IllegalArgumentException("Unable to parse url: " + url); throw new IllegalArgumentException("Unable to parse url: " + url, ex);
iae.initCause(ex);
throw iae;
} }
} }

View File

@ -37,6 +37,7 @@ import javax.naming.directory.SearchResult;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.ContextExecutor; import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.ContextMapper; import org.springframework.ldap.core.ContextMapper;
@ -46,6 +47,7 @@ import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.LdapTemplate;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/** /**
* Extension of Spring LDAP's LdapTemplate class which adds extra functionality required * Extension of Spring LDAP's LdapTemplate class which adds extra functionality required
@ -76,7 +78,6 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
public SpringSecurityLdapTemplate(ContextSource contextSource) { public SpringSecurityLdapTemplate(ContextSource contextSource) {
Assert.notNull(contextSource, "ContextSource cannot be null"); Assert.notNull(contextSource, "ContextSource cannot be null");
setContextSource(contextSource); setContextSource(contextSource);
this.searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); this.searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
} }
@ -88,31 +89,18 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
* @param value the value to be checked against the directory value * @param value the value to be checked against the directory value
* @return true if the supplied value matches that in the directory * @return true if the supplied value matches that in the directory
*/ */
public boolean compare(final String dn, final String attributeName, final Object value) { public boolean compare(String dn, String attributeName, Object value) {
final String comparisonFilter = "(" + attributeName + "={0})"; String comparisonFilter = "(" + attributeName + "={0})";
return executeReadOnly((ctx) -> {
class LdapCompareCallback implements ContextExecutor { SearchControls searchControls = new SearchControls();
searchControls.setReturningAttributes(NO_ATTRS);
@Override searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
public Object executeWithContext(DirContext ctx) throws NamingException { Object[] params = new Object[] { value };
SearchControls ctls = new SearchControls(); NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, params, searchControls);
ctls.setReturningAttributes(NO_ATTRS); Boolean match = results.hasMore();
ctls.setSearchScope(SearchControls.OBJECT_SCOPE); LdapUtils.closeEnumeration(results);
return match;
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, new Object[] { value }, });
ctls);
Boolean match = results.hasMore();
LdapUtils.closeEnumeration(results);
return match;
}
}
Boolean matches = (Boolean) executeReadOnly(new LdapCompareCallback());
return matches;
} }
/** /**
@ -123,12 +111,8 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
* @return the object created by the mapper * @return the object created by the mapper
*/ */
public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) { public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) {
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> { return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve); Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
// Object object = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
return new DirContextAdapter(attrs, new DistinguishedName(dn), return new DirContextAdapter(attrs, new DistinguishedName(dn),
new DistinguishedName(ctx.getNameInNamespace())); new DistinguishedName(ctx.getNameInNamespace()));
}); });
@ -174,27 +158,23 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
* entries. The attribute name is the key for each set of values. In addition each map * entries. The attribute name is the key for each set of values. In addition each map
* contains the DN as a String with the key predefined key {@link #DN_KEY}. * contains the DN as a String with the key predefined key {@link #DN_KEY}.
*/ */
public Set<Map<String, List<String>>> searchForMultipleAttributeValues(final String base, final String filter, public Set<Map<String, List<String>>> searchForMultipleAttributeValues(String base, String filter, Object[] params,
final Object[] params, final String[] attributeNames) { String[] attributeNames) {
// Escape the params acording to RFC2254 // Escape the params acording to RFC2254
Object[] encodedParams = new String[params.length]; Object[] encodedParams = new String[params.length];
for (int i = 0; i < params.length; i++) { for (int i = 0; i < params.length; i++) {
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString()); encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
} }
String formattedFilter = MessageFormat.format(filter, encodedParams); String formattedFilter = MessageFormat.format(filter, encodedParams);
logger.debug("Using filter: " + formattedFilter); logger.debug(LogMessage.format("Using filter: %s", formattedFilter));
HashSet<Map<String, List<String>>> result = new HashSet<>();
final HashSet<Map<String, List<String>>> set = new HashSet<>();
ContextMapper roleMapper = (ctx) -> { ContextMapper roleMapper = (ctx) -> {
DirContextAdapter adapter = (DirContextAdapter) ctx; DirContextAdapter adapter = (DirContextAdapter) ctx;
Map<String, List<String>> record = new HashMap<>(); Map<String, List<String>> record = new HashMap<>();
if (attributeNames == null || attributeNames.length == 0) { if (ObjectUtils.isEmpty(attributeNames)) {
try { try {
for (NamingEnumeration ae = adapter.getAttributes().getAll(); ae.hasMore();) { for (NamingEnumeration enumeration = adapter.getAttributes().getAll(); enumeration.hasMore();) {
Attribute attr = (Attribute) ae.next(); Attribute attr = (Attribute) enumeration.next();
extractStringAttributeValues(adapter, record, attr.getID()); extractStringAttributeValues(adapter, record, attr.getID());
} }
} }
@ -208,17 +188,14 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
} }
} }
record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter))); record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter)));
set.add(record); result.add(record);
return null; return null;
}; };
SearchControls ctls = new SearchControls(); SearchControls ctls = new SearchControls();
ctls.setSearchScope(this.searchControls.getSearchScope()); ctls.setSearchScope(this.searchControls.getSearchScope());
ctls.setReturningAttributes((attributeNames != null && attributeNames.length > 0) ? attributeNames : null); ctls.setReturningAttributes((attributeNames != null && attributeNames.length > 0) ? attributeNames : null);
search(base, formattedFilter, ctls, roleMapper); search(base, formattedFilter, ctls, roleMapper);
return result;
return set;
} }
/** /**
@ -246,27 +223,23 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
String attributeName) { String attributeName) {
Object[] values = adapter.getObjectAttributes(attributeName); Object[] values = adapter.getObjectAttributes(attributeName);
if (values == null || values.length == 0) { if (values == null || values.length == 0) {
if (logger.isDebugEnabled()) { logger.debug(LogMessage.format("No attribute value found for '%s'", attributeName));
logger.debug("No attribute value found for '" + attributeName + "'");
}
return; return;
} }
List<String> svalues = new ArrayList<>(); List<String> stringValues = new ArrayList<>();
for (Object o : values) { for (Object value : values) {
if (o != null) { if (value != null) {
if (String.class.isAssignableFrom(o.getClass())) { if (String.class.isAssignableFrom(value.getClass())) {
svalues.add((String) o); stringValues.add((String) value);
} }
else { else {
if (logger.isDebugEnabled()) { logger.debug(LogMessage.format("Attribute:%s contains a non string value of type[%s]",
logger.debug("Attribute:" + attributeName + " contains a non string value of type[" attributeName, value.getClass()));
+ o.getClass() + "]"); stringValues.add(value.toString());
}
svalues.add(o.toString());
} }
} }
} }
record.put(attributeName, svalues); record.put(attributeName, stringValues);
} }
/** /**
@ -283,8 +256,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
* @throws IncorrectResultSizeDataAccessException if no results are found or the * @throws IncorrectResultSizeDataAccessException if no results are found or the
* search returns more than one result. * search returns more than one result.
*/ */
public DirContextOperations searchForSingleEntry(final String base, final String filter, final Object[] params) { public DirContextOperations searchForSingleEntry(String base, String filter, Object[] params) {
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> searchForSingleEntryInternal(ctx, return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> searchForSingleEntryInternal(ctx,
this.searchControls, base, filter, params)); this.searchControls, base, filter, params));
} }
@ -298,22 +270,15 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
final DistinguishedName searchBaseDn = new DistinguishedName(base); final DistinguishedName searchBaseDn = new DistinguishedName(base);
final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params, final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params,
buildControls(searchControls)); buildControls(searchControls));
logger.debug(LogMessage.format("Searching for entry under DN '%s', base = '%s', filter = '%s'", ctxBaseDn,
if (logger.isDebugEnabled()) { searchBaseDn, filter));
logger.debug("Searching for entry under DN '" + ctxBaseDn + "', base = '" + searchBaseDn + "', filter = '"
+ filter + "'");
}
Set<DirContextOperations> results = new HashSet<>(); Set<DirContextOperations> results = new HashSet<>();
try { try {
while (resultsEnum.hasMore()) { while (resultsEnum.hasMore()) {
SearchResult searchResult = resultsEnum.next(); SearchResult searchResult = resultsEnum.next();
DirContextAdapter dca = (DirContextAdapter) searchResult.getObject(); DirContextAdapter dca = (DirContextAdapter) searchResult.getObject();
Assert.notNull(dca, "No object returned by search, DirContext is not correctly configured"); Assert.notNull(dca, "No object returned by search, DirContext is not correctly configured");
logger.debug(LogMessage.format("Found DN: %s", dca.getDn()));
if (logger.isDebugEnabled()) {
logger.debug("Found DN: " + dca.getDn());
}
results.add(dca); results.add(dca);
} }
} }
@ -321,15 +286,9 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
LdapUtils.closeEnumeration(resultsEnum); LdapUtils.closeEnumeration(resultsEnum);
logger.info("Ignoring PartialResultException"); logger.info("Ignoring PartialResultException");
} }
if (results.size() != 1) {
if (results.size() == 0) {
throw new IncorrectResultSizeDataAccessException(1, 0);
}
if (results.size() > 1) {
throw new IncorrectResultSizeDataAccessException(1, results.size()); throw new IncorrectResultSizeDataAccessException(1, results.size());
} }
return results.iterator().next(); return results.iterator().next();
} }

View File

@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware; import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor; import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
@ -64,33 +65,22 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> this.messages.getMessage("LdapAuthenticationProvider.onlySupports", () -> this.messages.getMessage("LdapAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported")); "Only UsernamePasswordAuthenticationToken is supported"));
UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;
final UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;
String username = userToken.getName(); String username = userToken.getName();
String password = (String) authentication.getCredentials(); String password = (String) authentication.getCredentials();
this.logger.debug(LogMessage.format("Processing authentication request for user: %s", username));
if (this.logger.isDebugEnabled()) {
this.logger.debug("Processing authentication request for user: " + username);
}
if (!StringUtils.hasLength(username)) { if (!StringUtils.hasLength(username)) {
throw new BadCredentialsException( throw new BadCredentialsException(
this.messages.getMessage("LdapAuthenticationProvider.emptyUsername", "Empty Username")); this.messages.getMessage("LdapAuthenticationProvider.emptyUsername", "Empty Username"));
} }
if (!StringUtils.hasLength(password)) { if (!StringUtils.hasLength(password)) {
throw new BadCredentialsException( throw new BadCredentialsException(
this.messages.getMessage("AbstractLdapAuthenticationProvider.emptyPassword", "Empty Password")); this.messages.getMessage("AbstractLdapAuthenticationProvider.emptyPassword", "Empty Password"));
} }
Assert.notNull(password, "Null password was supplied in authentication token"); Assert.notNull(password, "Null password was supplied in authentication token");
DirContextOperations userData = doAuthentication(userToken); DirContextOperations userData = doAuthentication(userToken);
UserDetails user = this.userDetailsContextMapper.mapUserFromContext(userData, authentication.getName(), UserDetails user = this.userDetailsContextMapper.mapUserFromContext(userData, authentication.getName(),
loadUserAuthorities(userData, authentication.getName(), (String) authentication.getCredentials())); loadUserAuthorities(userData, authentication.getName(), (String) authentication.getCredentials()));
return createSuccessfulAuthentication(userToken, user); return createSuccessfulAuthentication(userToken, user);
} }
@ -111,11 +101,9 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati
UserDetails user) { UserDetails user) {
Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials() Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials()
: user.getPassword(); : user.getPassword();
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password, UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password,
this.authoritiesMapper.mapAuthorities(user.getAuthorities())); this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails()); result.setDetails(authentication.getDetails());
return result; return result;
} }

View File

@ -91,16 +91,13 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
if (this.userDnFormat == null) { if (this.userDnFormat == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<String> userDns = new ArrayList<>(this.userDnFormat.length); List<String> userDns = new ArrayList<>(this.userDnFormat.length);
String[] args = new String[] { LdapEncoder.nameEncode(username) }; String[] args = new String[] { LdapEncoder.nameEncode(username) };
synchronized (this.userDnFormat) { synchronized (this.userDnFormat) {
for (MessageFormat formatter : this.userDnFormat) { for (MessageFormat formatter : this.userDnFormat) {
userDns.add(formatter.format(args)); userDns.add(formatter.format(args));
} }
} }
return userDns; return userDns;
} }
@ -134,7 +131,6 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null"); Assert.notNull(dnPattern, "The array of DN patterns cannot be set to null");
// this.userDnPattern = dnPattern; // this.userDnPattern = dnPattern;
this.userDnFormat = new MessageFormat[dnPattern.length]; this.userDnFormat = new MessageFormat[dnPattern.length];
for (int i = 0; i < dnPattern.length; i++) { for (int i = 0; i < dnPattern.length; i++) {
this.userDnFormat[i] = new MessageFormat(dnPattern[i]); this.userDnFormat[i] = new MessageFormat(dnPattern[i]);
} }

View File

@ -22,6 +22,7 @@ import javax.naming.directory.DirContext;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.NamingException; import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
@ -51,7 +52,6 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
* provided. * provided.
* @param contextSource the BaseLdapPathContextSource instance against which bind * @param contextSource the BaseLdapPathContextSource instance against which bind
* operations will be performed. * operations will be performed.
*
*/ */
public BindAuthenticator(BaseLdapPathContextSource contextSource) { public BindAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource); super(contextSource);
@ -62,37 +62,30 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
DirContextOperations user = null; DirContextOperations user = null;
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects"); "Can only process UsernamePasswordAuthenticationToken objects");
String username = authentication.getName(); String username = authentication.getName();
String password = (String) authentication.getCredentials(); String password = (String) authentication.getCredentials();
if (!StringUtils.hasLength(password)) { if (!StringUtils.hasLength(password)) {
logger.debug("Rejecting empty password for user " + username); logger.debug(LogMessage.format("Rejecting empty password for user %s", username));
throw new BadCredentialsException( throw new BadCredentialsException(
this.messages.getMessage("BindAuthenticator.emptyPassword", "Empty Password")); this.messages.getMessage("BindAuthenticator.emptyPassword", "Empty Password"));
} }
// If DN patterns are configured, try authenticating with them directly // If DN patterns are configured, try authenticating with them directly
for (String dn : getUserDns(username)) { for (String dn : getUserDns(username)) {
user = bindWithDn(dn, username, password); user = bindWithDn(dn, username, password);
if (user != null) { if (user != null) {
break; break;
} }
} }
// Otherwise use the configured search object to find the user and authenticate // Otherwise use the configured search object to find the user and authenticate
// with the returned DN. // with the returned DN.
if (user == null && getUserSearch() != null) { if (user == null && getUserSearch() != null) {
DirContextOperations userFromSearch = getUserSearch().searchForUser(username); DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
user = bindWithDn(userFromSearch.getDn().toString(), username, password, userFromSearch.getAttributes()); user = bindWithDn(userFromSearch.getDn().toString(), username, password, userFromSearch.getAttributes());
} }
if (user == null) { if (user == null) {
throw new BadCredentialsException( throw new BadCredentialsException(
this.messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials")); this.messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
} }
return user; return user;
} }
@ -105,26 +98,20 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
DistinguishedName userDn = new DistinguishedName(userDnStr); DistinguishedName userDn = new DistinguishedName(userDnStr);
DistinguishedName fullDn = new DistinguishedName(userDn); DistinguishedName fullDn = new DistinguishedName(userDn);
fullDn.prepend(ctxSource.getBaseLdapPath()); fullDn.prepend(ctxSource.getBaseLdapPath());
logger.debug(LogMessage.format("Attempting to bind as %s", fullDn));
logger.debug("Attempting to bind as " + fullDn);
DirContext ctx = null; DirContext ctx = null;
try { try {
ctx = getContextSource().getContext(fullDn.toString(), password); ctx = getContextSource().getContext(fullDn.toString(), password);
// Check for password policy control // Check for password policy control
PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx); PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx);
logger.debug("Retrieving attributes..."); logger.debug("Retrieving attributes...");
if (attrs == null || attrs.size() == 0) { if (attrs == null || attrs.size() == 0) {
attrs = ctx.getAttributes(userDn, getUserAttributes()); attrs = ctx.getAttributes(userDn, getUserAttributes());
} }
DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath()); DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath());
if (ppolicy != null) { if (ppolicy != null) {
result.setAttributeValue(ppolicy.getID(), ppolicy); result.setAttributeValue(ppolicy.getID(), ppolicy);
} }
return result; return result;
} }
catch (NamingException ex) { catch (NamingException ex) {
@ -145,7 +132,6 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
finally { finally {
LdapUtils.closeContext(ctx); LdapUtils.closeContext(ctx);
} }
return null; return null;
} }
@ -155,9 +141,7 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
* logger. * logger.
*/ */
protected void handleBindException(String userDn, String username, Throwable cause) { protected void handleBindException(String userDn, String username, Throwable cause) {
if (logger.isDebugEnabled()) { logger.debug(LogMessage.format("Failed to bind as %s: %s", userDn, cause));
logger.debug("Failed to bind as " + userDn + ": " + cause);
}
} }
} }

View File

@ -173,23 +173,21 @@ public class LdapAuthenticationProvider extends AbstractLdapAuthenticationProvid
try { try {
return getAuthenticator().authenticate(authentication); return getAuthenticator().authenticate(authentication);
} }
catch (PasswordPolicyException ppe) { catch (PasswordPolicyException ex) {
// The only reason a ppolicy exception can occur during a bind is that the // The only reason a ppolicy exception can occur during a bind is that the
// account is locked. // account is locked.
throw new LockedException( throw new LockedException(
this.messages.getMessage(ppe.getStatus().getErrorCode(), ppe.getStatus().getDefaultMessage())); this.messages.getMessage(ex.getStatus().getErrorCode(), ex.getStatus().getDefaultMessage()));
} }
catch (UsernameNotFoundException notFound) { catch (UsernameNotFoundException ex) {
if (this.hideUserNotFoundExceptions) { if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException( throw new BadCredentialsException(
this.messages.getMessage("LdapAuthenticationProvider.badCredentials", "Bad credentials")); this.messages.getMessage("LdapAuthenticationProvider.badCredentials", "Bad credentials"));
} }
else { throw ex;
throw notFound;
}
} }
catch (NamingException ldapAccessFailure) { catch (NamingException ex) {
throw new InternalAuthenticationServiceException(ldapAccessFailure.getMessage(), ldapAccessFailure); throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
} }
} }

View File

@ -34,18 +34,11 @@ final class LdapEncoder {
private static final int HEX = 16; private static final int HEX = 16;
private static String[] NAME_ESCAPE_TABLE = new String[96]; private static String[] NAME_ESCAPE_TABLE = new String[96];
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static { static {
// Name encoding table -------------------------------------
// all below 0x20 (control chars) // all below 0x20 (control chars)
for (char c = 0; c < ' '; c++) { for (char c = 0; c < ' '; c++) {
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(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[';'] = "\\;";
@ -55,21 +48,21 @@ final class LdapEncoder {
NAME_ESCAPE_TABLE['>'] = "\\>"; NAME_ESCAPE_TABLE['>'] = "\\>";
NAME_ESCAPE_TABLE['\"'] = "\\\""; NAME_ESCAPE_TABLE['\"'] = "\\\"";
NAME_ESCAPE_TABLE['\\'] = "\\\\"; NAME_ESCAPE_TABLE['\\'] = "\\\\";
}
// Filter encoding table ------------------------------------- private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static {
// fill with char itself // fill with char itself
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) { for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c); FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
} }
// escapes (RFC2254) // escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a"; FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28"; FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29"; FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c"; FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00"; FILTER_ESCAPE_TABLE[0] = "\\00";
} }
/** /**
@ -79,15 +72,8 @@ final class LdapEncoder {
} }
protected static String toTwoCharHex(char c) { protected static String toTwoCharHex(char c) {
String raw = Integer.toHexString(c).toUpperCase(); String raw = Integer.toHexString(c).toUpperCase();
return (raw.length() > 1) ? raw : "0" + raw;
if (raw.length() > 1) {
return raw;
}
else {
return "0" + raw;
}
} }
/** /**
@ -96,29 +82,15 @@ final class LdapEncoder {
* @return a properly escaped representation of the supplied value. * @return a properly escaped representation of the supplied value.
*/ */
static String filterEncode(String value) { static String filterEncode(String value) {
if (value == null) { if (value == null) {
return null; return null;
} }
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2); StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length(); int length = value.length();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
char ch = value.charAt(i);
char c = value.charAt(i); encodedValue.append((ch < FILTER_ESCAPE_TABLE.length) ? FILTER_ESCAPE_TABLE[ch] : ch);
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
}
else {
// default: add the char
encodedValue.append(c);
}
} }
return encodedValue.toString(); return encodedValue.toString();
} }
@ -141,43 +113,31 @@ final class LdapEncoder {
* @return The escaped value. * @return The escaped value.
*/ */
static String nameEncode(String value) { static String nameEncode(String value) {
if (value == null) { if (value == null) {
return null; return null;
} }
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2); StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length(); int length = value.length();
int last = length - 1; int last = length - 1;
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
char c = value.charAt(i); char c = value.charAt(i);
// space first or last // space first or last
if (c == ' ' && (i == 0 || i == last)) { if (c == ' ' && (i == 0 || i == last)) {
encodedValue.append("\\ "); encodedValue.append("\\ ");
continue; continue;
} }
// check in table for escapes
if (c < NAME_ESCAPE_TABLE.length) { if (c < NAME_ESCAPE_TABLE.length) {
// check in table for escapes
String esc = NAME_ESCAPE_TABLE[c]; String esc = NAME_ESCAPE_TABLE[c];
if (esc != null) { if (esc != null) {
encodedValue.append(esc); encodedValue.append(esc);
continue; continue;
} }
} }
// default: add the char // default: add the char
encodedValue.append(c); encodedValue.append(c);
} }
return encodedValue.toString(); return encodedValue.toString();
} }
/** /**
@ -188,43 +148,32 @@ final class LdapEncoder {
* @throws BadLdapGrammarException * @throws BadLdapGrammarException
*/ */
static String nameDecode(String value) throws BadLdapGrammarException { static String nameDecode(String value) throws BadLdapGrammarException {
if (value == null) { if (value == null) {
return null; return null;
} }
// make buffer same size
StringBuilder decoded = new StringBuilder(value.length()); StringBuilder decoded = new StringBuilder(value.length());
int i = 0; int i = 0;
while (i < value.length()) { while (i < value.length()) {
char currentChar = value.charAt(i); char currentChar = value.charAt(i);
if (currentChar == '\\') { if (currentChar == '\\') {
// Ending with a single backslash is not allowed
if (value.length() <= i + 1) { if (value.length() <= i + 1) {
// Ending with a single backslash is not allowed
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'"); throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
} }
char nextChar = value.charAt(i + 1);
if (isNormalBackslashEscape(nextChar)) {
decoded.append(nextChar);
i += 2;
}
else { else {
char nextChar = value.charAt(i + 1); if (value.length() <= i + 2) {
if (nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>' throw new BadLdapGrammarException(
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' "Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
|| nextChar == ' ') {
// Normal backslash escape
decoded.append(nextChar);
i += 2;
}
else {
if (value.length() <= i + 2) {
throw new BadLdapGrammarException(
"Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
}
else {
// This should be a hex value
String hexString = "" + nextChar + value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString, HEX));
i += 3;
}
} }
// This should be a hex value
String hexString = "" + nextChar + value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString, HEX));
i += 3;
} }
} }
else { else {
@ -238,4 +187,9 @@ final class LdapEncoder {
} }
private static boolean isNormalBackslashEscape(char nextChar) {
return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' ';
}
} }

View File

@ -19,6 +19,7 @@ package org.springframework.security.ldap.authentication;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.NameNotFoundException; import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource; import org.springframework.ldap.core.support.BaseLdapPathContextSource;
@ -66,13 +67,10 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects"); "Can only process UsernamePasswordAuthenticationToken objects");
// locate the user and check the password // locate the user and check the password
DirContextOperations user = null; DirContextOperations user = null;
String username = authentication.getName(); String username = authentication.getName();
String password = (String) authentication.getCredentials(); String password = (String) authentication.getCredentials();
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource()); SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource());
for (String userDn : getUserDns(username)) { for (String userDn : getUserDns(username)) {
try { try {
user = ldapTemplate.retrieveEntry(userDn, getUserAttributes()); user = ldapTemplate.retrieveEntry(userDn, getUserAttributes());
@ -83,24 +81,20 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic
break; break;
} }
} }
if (user == null && getUserSearch() != null) { if (user == null && getUserSearch() != null) {
user = getUserSearch().searchForUser(username); user = getUserSearch().searchForUser(username);
} }
if (user == null) { if (user == null) {
throw new UsernameNotFoundException("User not found: " + username); throw new UsernameNotFoundException("User not found: " + username);
} }
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Performing LDAP compare of password attribute '" + this.passwordAttributeName + "' for user '" logger.debug(LogMessage.format("Performing LDAP compare of password attribute '%s' for user '%s'",
+ user.getDn() + "'"); this.passwordAttributeName, user.getDn()));
} }
if (this.usePasswordAttrCompare && isPasswordAttrCompare(user, password)) { if (this.usePasswordAttrCompare && isPasswordAttrCompare(user, password)) {
return user; return user;
} }
else if (isLdapPasswordCompare(user, ldapTemplate, password)) { if (isLdapPasswordCompare(user, ldapTemplate, password)) {
return user; return user;
} }
throw new BadCredentialsException( throw new BadCredentialsException(

View File

@ -47,28 +47,21 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource
@Override @Override
public String getPrincipal() { public String getPrincipal() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) { if (authentication == null) {
log.warn("No Authentication object set in SecurityContext - returning empty String as Principal"); log.warn("No Authentication object set in SecurityContext - returning empty String as Principal");
return ""; return "";
} }
Object principal = authentication.getPrincipal(); Object principal = authentication.getPrincipal();
if (principal instanceof LdapUserDetails) { if (principal instanceof LdapUserDetails) {
LdapUserDetails details = (LdapUserDetails) principal; LdapUserDetails details = (LdapUserDetails) principal;
return details.getDn(); return details.getDn();
} }
else if (authentication instanceof AnonymousAuthenticationToken) { if (authentication instanceof AnonymousAuthenticationToken) {
if (log.isDebugEnabled()) { log.debug("Anonymous Authentication, returning empty String as Principal");
log.debug("Anonymous Authentication, returning empty String as Principal");
}
return ""; return "";
} }
else { throw new IllegalArgumentException(
throw new IllegalArgumentException( "The principal property of the authentication object" + "needs to be an LdapUserDetails.");
"The principal property of the authentication object" + "needs to be an LdapUserDetails.");
}
} }
/** /**
@ -77,12 +70,10 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource
@Override @Override
public String getCredentials() { public String getCredentials() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) { if (authentication == null) {
log.warn("No Authentication object set in SecurityContext - returning empty String as Credentials"); log.warn("No Authentication object set in SecurityContext - returning empty String as Credentials");
return ""; return "";
} }
return (String) authentication.getCredentials(); return (String) authentication.getCredentials();
} }

View File

@ -22,6 +22,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -34,6 +35,7 @@ import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls; import javax.naming.directory.SearchControls;
import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.InitialLdapContext;
import org.springframework.core.log.LogMessage;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.CommunicationException; import org.springframework.ldap.CommunicationException;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
@ -161,7 +163,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
String username = auth.getName(); String username = auth.getName();
String password = (String) auth.getCredentials(); String password = (String) auth.getCredentials();
DirContext ctx = null; DirContext ctx = null;
try { try {
ctx = bindAsUser(username, password); ctx = bindAsUser(username, password);
return searchForUser(ctx, username); return searchForUser(ctx, username);
@ -186,30 +187,23 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username, protected Collection<? extends GrantedAuthority> loadUserAuthorities(DirContextOperations userData, String username,
String password) { String password) {
String[] groups = userData.getStringAttributes("memberOf"); String[] groups = userData.getStringAttributes("memberOf");
if (groups == null) { if (groups == null) {
this.logger.debug("No values for 'memberOf' attribute."); this.logger.debug("No values for 'memberOf' attribute.");
return AuthorityUtils.NO_AUTHORITIES; return AuthorityUtils.NO_AUTHORITIES;
} }
if (this.logger.isDebugEnabled()) { if (this.logger.isDebugEnabled()) {
this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups)); this.logger.debug("'memberOf' attribute values: " + Arrays.asList(groups));
} }
List<GrantedAuthority> authorities = new ArrayList<>(groups.length);
ArrayList<GrantedAuthority> authorities = new ArrayList<>(groups.length);
for (String group : groups) { for (String group : groups) {
authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue())); authorities.add(new SimpleGrantedAuthority(new DistinguishedName(group).removeLast().getValue()));
} }
return authorities; return authorities;
} }
private DirContext bindAsUser(String username, String password) { private DirContext bindAsUser(String username, String password) {
// TODO. add DNS lookup based on domain // TODO. add DNS lookup based on domain
final String bindUrl = this.url; final String bindUrl = this.url;
Hashtable<String, Object> env = new Hashtable<>(); Hashtable<String, Object> env = new Hashtable<>();
env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_AUTHENTICATION, "simple");
String bindPrincipal = createBindPrincipal(username); String bindPrincipal = createBindPrincipal(username);
@ -219,7 +213,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName()); env.put(Context.OBJECT_FACTORIES, DefaultDirObjectFactory.class.getName());
env.putAll(this.contextEnvironmentProperties); env.putAll(this.contextEnvironmentProperties);
try { try {
return this.contextFactory.createContext(env); return this.contextFactory.createContext(env);
} }
@ -228,28 +221,20 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
handleBindException(bindPrincipal, ex); handleBindException(bindPrincipal, ex);
throw badCredentials(ex); throw badCredentials(ex);
} }
else { throw LdapUtils.convertLdapException(ex);
throw LdapUtils.convertLdapException(ex);
}
} }
} }
private void handleBindException(String bindPrincipal, NamingException exception) { private void handleBindException(String bindPrincipal, NamingException exception) {
if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Authentication for %s failed:%s", bindPrincipal, exception));
this.logger.debug("Authentication for " + bindPrincipal + " failed:" + exception);
}
handleResolveObj(exception); handleResolveObj(exception);
int subErrorCode = parseSubErrorCode(exception.getMessage()); int subErrorCode = parseSubErrorCode(exception.getMessage());
if (subErrorCode <= 0) { if (subErrorCode <= 0) {
this.logger.debug("Failed to locate AD-specific sub-error code in message"); this.logger.debug("Failed to locate AD-specific sub-error code in message");
return; return;
} }
this.logger.info(
this.logger.info("Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode)); LogMessage.of(() -> "Active Directory authentication failed: " + subCodeToLogMessage(subErrorCode)));
if (this.convertSubErrorCodesToExceptions) { if (this.convertSubErrorCodesToExceptions) {
raiseExceptionForErrorCode(subErrorCode, exception); raiseExceptionForErrorCode(subErrorCode, exception);
} }
@ -264,12 +249,10 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
} }
private int parseSubErrorCode(String message) { private int parseSubErrorCode(String message) {
Matcher m = SUB_ERROR_CODE.matcher(message); Matcher matcher = SUB_ERROR_CODE.matcher(message);
if (matcher.matches()) {
if (m.matches()) { return Integer.parseInt(matcher.group(1), 16);
return Integer.parseInt(m.group(1), 16);
} }
return -1; return -1;
} }
@ -313,7 +296,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
case ACCOUNT_LOCKED: case ACCOUNT_LOCKED:
return "Account locked"; return "Account locked";
} }
return "Unknown (error code " + Integer.toHexString(code) + ")"; return "Unknown (error code " + Integer.toHexString(code) + ")";
} }
@ -334,7 +316,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
private DirContextOperations searchForUser(DirContext context, String username) throws NamingException { private DirContextOperations searchForUser(DirContext context, String username) throws NamingException {
SearchControls searchControls = new SearchControls(); SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String bindPrincipal = createBindPrincipal(username); String bindPrincipal = createBindPrincipal(username);
String searchRoot = (this.rootDn != null) ? this.rootDn : searchRootFromPrincipal(bindPrincipal); String searchRoot = (this.rootDn != null) ? this.rootDn : searchRootFromPrincipal(bindPrincipal);
@ -342,45 +323,40 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context, searchControls, searchRoot, return SpringSecurityLdapTemplate.searchForSingleEntryInternal(context, searchControls, searchRoot,
this.searchFilter, new Object[] { bindPrincipal, username }); this.searchFilter, new Object[] { bindPrincipal, username });
} }
catch (CommunicationException ldapCommunicationException) { catch (CommunicationException ex) {
throw badLdapConnection(ldapCommunicationException); throw badLdapConnection(ex);
} }
catch (IncorrectResultSizeDataAccessException incorrectResults) { catch (IncorrectResultSizeDataAccessException ex) {
// Search should never return multiple results if properly configured - just // Search should never return multiple results if properly configured -
// rethrow if (ex.getActualSize() != 0) {
if (incorrectResults.getActualSize() != 0) { throw ex;
throw incorrectResults;
} }
// If we found no results, then the username/password did not match // If we found no results, then the username/password did not match
UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException( UsernameNotFoundException userNameNotFoundException = new UsernameNotFoundException(
"User " + username + " not found in directory.", incorrectResults); "User " + username + " not found in directory.", ex);
throw badCredentials(userNameNotFoundException); throw badCredentials(userNameNotFoundException);
} }
} }
private String searchRootFromPrincipal(String bindPrincipal) { private String searchRootFromPrincipal(String bindPrincipal) {
int atChar = bindPrincipal.lastIndexOf('@'); int atChar = bindPrincipal.lastIndexOf('@');
if (atChar < 0) { if (atChar < 0) {
this.logger.debug("User principal '" + bindPrincipal this.logger.debug("User principal '" + bindPrincipal
+ "' does not contain the domain, and no domain has been configured"); + "' does not contain the domain, and no domain has been configured");
throw badCredentials(); throw badCredentials();
} }
return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length())); return rootDnFromDomain(bindPrincipal.substring(atChar + 1, bindPrincipal.length()));
} }
private String rootDnFromDomain(String domain) { private String rootDnFromDomain(String domain) {
String[] tokens = StringUtils.tokenizeToStringArray(domain, "."); String[] tokens = StringUtils.tokenizeToStringArray(domain, ".");
StringBuilder root = new StringBuilder(); StringBuilder root = new StringBuilder();
for (String token : tokens) { for (String token : tokens) {
if (root.length() > 0) { if (root.length() > 0) {
root.append(','); root.append(',');
} }
root.append("dc=").append(token); root.append("dc=").append(token);
} }
return root.toString(); return root.toString();
} }
@ -388,7 +364,6 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
if (this.domain == null || username.toLowerCase().endsWith(this.domain)) { if (this.domain == null || username.toLowerCase().endsWith(this.domain)) {
return username; return username;
} }
return username + "@" + this.domain; return username + "@" + this.domain;
} }

View File

@ -23,6 +23,7 @@ import javax.naming.directory.DirContext;
import javax.naming.ldap.Control; import javax.naming.ldap.Control;
import javax.naming.ldap.LdapContext; import javax.naming.ldap.LdapContext;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.support.LdapUtils; import org.springframework.ldap.support.LdapUtils;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource; import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
@ -49,45 +50,30 @@ public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityConte
if (principal.equals(this.userDn)) { if (principal.equals(this.userDn)) {
return super.getContext(principal, credentials); return super.getContext(principal, credentials);
} }
this.logger
final boolean debug = this.logger.isDebugEnabled(); .debug(LogMessage.format("Binding as '%s', prior to reconnect as user '%s'", this.userDn, principal));
if (debug) {
this.logger.debug("Binding as '" + this.userDn + "', prior to reconnect as user '" + principal + "'");
}
// First bind as manager user before rebinding as the specific principal. // First bind as manager user before rebinding as the specific principal.
LdapContext ctx = (LdapContext) super.getContext(this.userDn, this.password); LdapContext ctx = (LdapContext) super.getContext(this.userDn, this.password);
Control[] rctls = { new PasswordPolicyControl(false) }; Control[] rctls = { new PasswordPolicyControl(false) };
try { try {
ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal); ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, principal);
ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials); ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, credentials);
ctx.reconnect(rctls); ctx.reconnect(rctls);
} }
catch (javax.naming.NamingException ne) { catch (javax.naming.NamingException ex) {
PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor.extractControl(ctx); PasswordPolicyResponseControl ctrl = PasswordPolicyControlExtractor.extractControl(ctx);
if (debug) { if (this.logger.isDebugEnabled()) {
this.logger.debug("Failed to obtain context", ne); this.logger.debug("Failed to obtain context", ex);
this.logger.debug("Password policy response: " + ctrl); this.logger.debug("Password policy response: " + ctrl);
} }
LdapUtils.closeContext(ctx); LdapUtils.closeContext(ctx);
if (ctrl != null && ctrl.isLocked()) {
if (ctrl != null) { throw new PasswordPolicyException(ctrl.getErrorStatus());
if (ctrl.isLocked()) {
throw new PasswordPolicyException(ctrl.getErrorStatus());
}
} }
throw LdapUtils.convertLdapException(ex);
throw LdapUtils.convertLdapException(ne);
} }
this.logger.debug(
if (debug) { LogMessage.of(() -> "PPolicy control returned: " + PasswordPolicyControlExtractor.extractControl(ctx)));
this.logger.debug("PPolicy control returned: " + PasswordPolicyControlExtractor.extractControl(ctx));
}
return ctx; return ctx;
} }
@ -95,9 +81,7 @@ public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityConte
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected Hashtable getAuthenticatedEnv(String principal, String credentials) { 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()); env.put(LdapContext.CONTROL_FACTORIES, PasswordPolicyControlFactory.class.getName());
return env; return env;
} }

View File

@ -32,7 +32,9 @@ import javax.naming.ldap.Control;
*/ */
public class PasswordPolicyControl implements Control { public class PasswordPolicyControl implements Control {
/** OID of the Password Policy Control */ /**
* OID of the Password Policy Control
*/
public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1"; public static final String OID = "1.3.6.1.4.1.42.2.27.8.5.1";
private final boolean critical; private final boolean critical;

View File

@ -45,13 +45,11 @@ public final class PasswordPolicyControlExtractor {
catch (javax.naming.NamingException ex) { catch (javax.naming.NamingException ex) {
logger.error("Failed to obtain response controls", ex); logger.error("Failed to obtain response controls", ex);
} }
for (int i = 0; ctrls != null && i < ctrls.length; i++) { for (int i = 0; ctrls != null && i < ctrls.length; i++) {
if (ctrls[i] instanceof PasswordPolicyResponseControl) { if (ctrls[i] instanceof PasswordPolicyResponseControl) {
return (PasswordPolicyResponseControl) ctrls[i]; return (PasswordPolicyResponseControl) ctrls[i];
} }
} }
return null; return null;
} }

View File

@ -39,7 +39,6 @@ public class PasswordPolicyControlFactory extends ControlFactory {
if (ctl.getID().equals(PasswordPolicyControl.OID)) { if (ctl.getID().equals(PasswordPolicyControl.OID)) {
return new PasswordPolicyResponseControl(ctl.getEncodedValue()); return new PasswordPolicyResponseControl(ctl.getEncodedValue());
} }
return null; return null;
} }

View File

@ -41,20 +41,24 @@ package org.springframework.security.ldap.ppolicy;
*/ */
public enum PasswordPolicyErrorStatus { public enum PasswordPolicyErrorStatus {
PASSWORD_EXPIRED("ppolicy.expired", "Your password has expired"), ACCOUNT_LOCKED("ppolicy.locked", PASSWORD_EXPIRED("ppolicy.expired", "Your password has expired"),
"Account is locked"), CHANGE_AFTER_RESET("ppolicy.change.after.reset",
"Your password must be changed after being reset"), PASSWORD_MOD_NOT_ALLOWED( ACCOUNT_LOCKED("ppolicy.locked", "Account is locked"),
"ppolicy.mod.not.allowed",
"Password cannot be changed"), MUST_SUPPLY_OLD_PASSWORD("ppolicy.must.supply.old.password", CHANGE_AFTER_RESET("ppolicy.change.after.reset", "Your password must be changed after being reset"),
"The old password must be supplied"), INSUFFICIENT_PASSWORD_QUALITY(
"ppolicy.insufficient.password.quality", PASSWORD_MOD_NOT_ALLOWED("ppolicy.mod.not.allowed", "Password cannot be changed"),
"The supplied password is of insufficient quality"), PASSWORD_TOO_SHORT(
"ppolicy.password.too.short", MUST_SUPPLY_OLD_PASSWORD("ppolicy.must.supply.old.password", "The old password must be supplied"),
"The supplied password is too short"), PASSWORD_TOO_YOUNG(
"ppolicy.password.too.young", INSUFFICIENT_PASSWORD_QUALITY("ppolicy.insufficient.password.quality",
"Your password was changed too recently to be changed again"), PASSWORD_IN_HISTORY( "The supplied password is of insufficient quality"),
"ppolicy.password.in.history",
"The supplied password has already been used"); PASSWORD_TOO_SHORT("ppolicy.password.too.short", "The supplied password is too short"),
PASSWORD_TOO_YOUNG("ppolicy.password.too.young", "Your password was changed too recently to be changed again"),
PASSWORD_IN_HISTORY("ppolicy.password.in.history", "The supplied password has already been used");
private final String errorCode; private final String errorCode;

View File

@ -77,10 +77,7 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
*/ */
public PasswordPolicyResponseControl(byte[] encodedValue) { public PasswordPolicyResponseControl(byte[] encodedValue) {
this.encodedValue = encodedValue; this.encodedValue = encodedValue;
// PPolicyDecoder decoder = new JLdapDecoder();
PPolicyDecoder decoder = new NetscapeDecoder(); PPolicyDecoder decoder = new NetscapeDecoder();
try { try {
decoder.decode(); decoder.decode();
} }
@ -162,23 +159,18 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder("PasswordPolicyResponseControl"); StringBuilder sb = new StringBuilder("PasswordPolicyResponseControl");
if (hasError()) { if (hasError()) {
sb.append(", error: ").append(this.errorStatus.getDefaultMessage()); sb.append(", error: ").append(this.errorStatus.getDefaultMessage());
} }
if (this.graceLoginsRemaining != Integer.MAX_VALUE) { if (this.graceLoginsRemaining != Integer.MAX_VALUE) {
sb.append(", warning: ").append(this.graceLoginsRemaining).append(" grace logins remain"); sb.append(", warning: ").append(this.graceLoginsRemaining).append(" grace logins remain");
} }
if (this.timeBeforeExpiration != Integer.MAX_VALUE) { if (this.timeBeforeExpiration != Integer.MAX_VALUE) {
sb.append(", warning: time before expiration is ").append(this.timeBeforeExpiration); sb.append(", warning: time before expiration is ").append(this.timeBeforeExpiration);
} }
if (!hasError() && !hasWarning()) { if (!hasError() && !hasWarning()) {
sb.append(" (no error, no warning)"); sb.append(" (no error, no warning)");
} }
return sb.toString(); return sb.toString();
} }
@ -198,24 +190,17 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
int[] bread = { 0 }; int[] bread = { 0 };
BERSequence seq = (BERSequence) BERElement.getElement(new SpecificTagDecoder(), BERSequence seq = (BERSequence) BERElement.getElement(new SpecificTagDecoder(),
new ByteArrayInputStream(PasswordPolicyResponseControl.this.encodedValue), bread); new ByteArrayInputStream(PasswordPolicyResponseControl.this.encodedValue), bread);
int size = seq.size(); int size = seq.size();
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("PasswordPolicyResponse, ASN.1 sequence has " + size + " elements"); logger.debug("PasswordPolicyResponse, ASN.1 sequence has " + size + " elements");
} }
for (int i = 0; i < seq.size(); i++) { for (int i = 0; i < seq.size(); i++) {
BERTag elt = (BERTag) seq.elementAt(i); BERTag elt = (BERTag) seq.elementAt(i);
int tag = elt.getTag() & 0x1F; int tag = elt.getTag() & 0x1F;
if (tag == 0) { if (tag == 0) {
BERChoice warning = (BERChoice) elt.getValue(); BERChoice warning = (BERChoice) elt.getValue();
BERTag content = (BERTag) warning.getValue(); BERTag content = (BERTag) warning.getValue();
int value = ((BERInteger) content.getValue()).getValue(); int value = ((BERInteger) content.getValue()).getValue();
if ((content.getTag() & 0x1F) == 0) { if ((content.getTag() & 0x1F) == 0) {
PasswordPolicyResponseControl.this.timeBeforeExpiration = value; PasswordPolicyResponseControl.this.timeBeforeExpiration = value;
} }
@ -241,19 +226,15 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
boolean[] implicit) throws IOException { boolean[] implicit) throws IOException {
tag &= 0x1F; tag &= 0x1F;
implicit[0] = false; implicit[0] = false;
if (tag == 0) { if (tag == 0) {
// Either the choice or the time before expiry within it // Either the choice or the time before expiry within it
if (this.inChoice == null) { if (this.inChoice == null) {
setInChoice(true); setInChoice(true);
// Read the choice length from the stream (ignored) // Read the choice length from the stream (ignored)
BERElement.readLengthOctets(stream, bytesRead); BERElement.readLengthOctets(stream, bytesRead);
int[] componentLength = new int[1]; int[] componentLength = new int[1];
BERElement choice = new BERChoice(decoder, stream, componentLength); BERElement choice = new BERChoice(decoder, stream, componentLength);
bytesRead[0] += componentLength[0]; bytesRead[0] += componentLength[0];
// inChoice = null; // inChoice = null;
return choice; return choice;
} }
@ -267,7 +248,6 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
if (this.inChoice == null) { if (this.inChoice == null) {
// The enumeration // The enumeration
setInChoice(false); setInChoice(false);
return new BEREnumerated(stream, bytesRead); return new BEREnumerated(stream, bytesRead);
} }
else { else {
@ -277,7 +257,6 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
} }
} }
} }
throw new DataRetrievalFailureException("Unexpected tag " + tag); throw new DataRetrievalFailureException("Unexpected tag " + tag);
} }
@ -289,67 +268,4 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
} }
/** Decoder based on the OpenLDAP/Novell JLDAP library */
// private class JLdapDecoder implements PPolicyDecoder {
//
// public void decode() throws IOException {
//
// LBERDecoder decoder = new LBERDecoder();
//
// ASN1Sequence seq = (ASN1Sequence)decoder.decode(encodedValue);
//
// if(seq == null) {
//
// }
//
// int size = seq.size();
//
// if(logger.isDebugEnabled()) {
// logger.debug("PasswordPolicyResponse, ASN.1 sequence has " +
// size + " elements");
// }
//
// for(int i=0; i < size; i++) {
//
// ASN1Tagged taggedObject = (ASN1Tagged)seq.get(i);
//
// int tag = taggedObject.getIdentifier().getTag();
//
// ASN1OctetString value = (ASN1OctetString)taggedObject.taggedValue();
// byte[] content = value.byteValue();
//
// if(tag == 0) {
// parseWarning(content, decoder);
//
// } else if(tag == 1) {
// // Error: set the code to the value
// errorCode = content[0];
// }
// }
// }
//
// private void parseWarning(byte[] content, LBERDecoder decoder) {
// // It's the warning (choice). Parse the number and set either the
// // expiry time or number of logins remaining.
// ASN1Tagged taggedObject = (ASN1Tagged)decoder.decode(content);
// int contentTag = taggedObject.getIdentifier().getTag();
// content = ((ASN1OctetString)taggedObject.taggedValue()).byteValue();
// int number;
//
// try {
// number = ((Long)decoder.decodeNumeric(new ByteArrayInputStream(content),
// content.length)).intValue();
// } catch(IOException ex) {
// throw new LdapDataAccessException("Failed to parse number ", ex);
// }
//
// if(contentTag == 0) {
// timeBeforeExpiration = number;
// } else if (contentTag == 1) {
// graceLoginsRemaining = number;
// }
// }
// }
} }

View File

@ -21,6 +21,7 @@ import javax.naming.directory.SearchControls;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
@ -73,13 +74,10 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
Assert.notNull(contextSource, "contextSource must not be null"); Assert.notNull(contextSource, "contextSource must not be null");
Assert.notNull(searchFilter, "searchFilter must not be null."); Assert.notNull(searchFilter, "searchFilter must not be null.");
Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable)."); Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable).");
this.searchFilter = searchFilter; this.searchFilter = searchFilter;
this.contextSource = contextSource; this.contextSource = contextSource;
this.searchBase = searchBase; this.searchBase = searchBase;
setSearchSubtree(true); setSearchSubtree(true);
if (searchBase.length() == 0) { if (searchBase.length() == 0) {
logger.info( logger.info(
"SearchBase not set. Searches will be performed from the root: " + contextSource.getBaseLdapPath()); "SearchBase not set. Searches will be performed from the root: " + contextSource.getBaseLdapPath());
@ -95,26 +93,18 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
*/ */
@Override @Override
public DirContextOperations searchForUser(String username) { public DirContextOperations searchForUser(String username) {
if (logger.isDebugEnabled()) { logger.debug(LogMessage.of(() -> "Searching for user '" + username + "', with user search " + this));
logger.debug("Searching for user '" + username + "', with user search " + this);
}
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(this.contextSource); SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(this.contextSource);
template.setSearchControls(this.searchControls); template.setSearchControls(this.searchControls);
try { try {
return template.searchForSingleEntry(this.searchBase, this.searchFilter, new String[] { username }); return template.searchForSingleEntry(this.searchBase, this.searchFilter, new String[] { username });
} }
catch (IncorrectResultSizeDataAccessException notFound) { catch (IncorrectResultSizeDataAccessException ex) {
if (notFound.getActualSize() == 0) { if (ex.getActualSize() == 0) {
throw new UsernameNotFoundException("User " + username + " not found in directory."); throw new UsernameNotFoundException("User " + username + " not found in directory.");
} }
// Search should never return multiple results if properly configured, so just // Search should never return multiple results if properly configured
// rethrow throw ex;
throw notFound;
} }
} }
@ -161,7 +151,6 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("[ searchFilter: '").append(this.searchFilter).append("', "); sb.append("[ searchFilter: '").append(this.searchFilter).append("', ");
sb.append("searchBase: '").append(this.searchBase).append("'"); sb.append("searchBase: '").append(this.searchBase).append("'");
sb.append(", scope: ").append( sb.append(", scope: ").append(

View File

@ -113,22 +113,12 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
this.ldifResources = ldifs; this.ldifResources = ldifs;
this.service = new DefaultDirectoryService(); this.service = new DefaultDirectoryService();
List<Interceptor> list = new ArrayList<>(); List<Interceptor> list = new ArrayList<>();
list.add(new NormalizationInterceptor()); list.add(new NormalizationInterceptor());
list.add(new AuthenticationInterceptor()); list.add(new AuthenticationInterceptor());
list.add(new ReferralInterceptor()); list.add(new ReferralInterceptor());
// list.add( new AciAuthorizationInterceptor() );
// list.add( new DefaultAuthorizationInterceptor() );
list.add(new ExceptionInterceptor()); list.add(new ExceptionInterceptor());
// list.add( new ChangeLogInterceptor() );
list.add(new OperationalAttributeInterceptor()); list.add(new OperationalAttributeInterceptor());
// list.add( new SchemaInterceptor() );
list.add(new SubentryInterceptor()); list.add(new SubentryInterceptor());
// list.add( new CollectiveAttributeInterceptor() );
// list.add( new EventInterceptor() );
// list.add( new TriggerInterceptor() );
// list.add( new JournalInterceptor() );
this.service.setInterceptors(list); this.service.setInterceptors(list);
this.partition = new JdbmPartition(); this.partition = new JdbmPartition();
this.partition.setId("rootPartition"); this.partition.setId("rootPartition");
@ -145,21 +135,16 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
if (this.workingDir == null) { if (this.workingDir == null) {
String apacheWorkDir = System.getProperty("apacheDSWorkDir"); String apacheWorkDir = System.getProperty("apacheDSWorkDir");
if (apacheWorkDir == null) { if (apacheWorkDir == null) {
apacheWorkDir = createTempDirectory("apacheds-spring-security-"); apacheWorkDir = createTempDirectory("apacheds-spring-security-");
} }
setWorkingDirectory(new File(apacheWorkDir)); setWorkingDirectory(new File(apacheWorkDir));
} }
if (this.ldapOverSslEnabled && this.keyStoreFile == null) { Assert.isTrue(!this.ldapOverSslEnabled || this.keyStoreFile != null,
throw new IllegalArgumentException("When LdapOverSsl is enabled, the keyStoreFile property must be set."); "When LdapOverSsl is enabled, the keyStoreFile property must be set.");
}
this.server = new LdapServer(); this.server = new LdapServer();
this.server.setDirectoryService(this.service); this.server.setDirectoryService(this.service);
// AbstractLdapIntegrationTests assume IPv4, so we specify the same here // AbstractLdapIntegrationTests assume IPv4, so we specify the same here
this.transport = new TcpTransport(this.port); this.transport = new TcpTransport(this.port);
if (this.ldapOverSslEnabled) { if (this.ldapOverSslEnabled) {
this.transport.setEnableSSL(true); this.transport.setEnableSSL(true);
@ -182,18 +167,13 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
public void setWorkingDirectory(File workingDir) { public void setWorkingDirectory(File workingDir) {
Assert.notNull(workingDir, "workingDir cannot be null"); Assert.notNull(workingDir, "workingDir cannot be null");
this.logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath()); this.logger.info("Setting working directory for LDAP_PROVIDER: " + workingDir.getAbsolutePath());
Assert.isTrue(!workingDir.exists(),
if (workingDir.exists()) { "The specified working directory '" + workingDir.getAbsolutePath()
throw new IllegalArgumentException("The specified working directory '" + workingDir.getAbsolutePath() + "' already exists. Another directory service instance may be using it or it may be from a "
+ "' already exists. Another directory service instance may be using it or it may be from a " + " previous unclean shutdown. Please confirm and delete it or configure a different "
+ " previous unclean shutdown. Please confirm and delete it or configure a different " + "working directory");
+ "working directory");
}
this.workingDir = workingDir; this.workingDir = workingDir;
this.service.setWorkingDirectory(workingDir); this.service.setWorkingDirectory(workingDir);
} }
@ -250,11 +230,7 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
if (isRunning()) { if (isRunning()) {
return; return;
} }
Assert.state(!this.service.isStarted(), "DirectoryService is already running.");
if (this.service.isStarted()) {
throw new IllegalStateException("DirectoryService is already running.");
}
this.logger.info("Starting directory server..."); this.logger.info("Starting directory server...");
try { try {
this.service.startup(); this.service.startup();
@ -263,7 +239,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
catch (Exception ex) { catch (Exception ex) {
throw new RuntimeException("Server startup failed", ex); throw new RuntimeException("Server startup failed", ex);
} }
try { try {
this.service.getAdminSession().lookup(this.partition.getSuffixDn()); this.service.getAdminSession().lookup(this.partition.getSuffixDn());
} }
@ -273,13 +248,10 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
catch (Exception ex) { catch (Exception ex) {
this.logger.error("Lookup failed", ex); this.logger.error("Lookup failed", ex);
} }
SocketAcceptor socketAcceptor = this.server.getSocketAcceptor(this.transport); SocketAcceptor socketAcceptor = this.server.getSocketAcceptor(this.transport);
InetSocketAddress localAddress = socketAcceptor.getLocalAddress(); InetSocketAddress localAddress = socketAcceptor.getLocalAddress();
this.localPort = localAddress.getPort(); this.localPort = localAddress.getPort();
this.running = true; this.running = true;
try { try {
importLdifs(); importLdifs();
} }
@ -308,7 +280,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
if (!isRunning()) { if (!isRunning()) {
return; return;
} }
this.logger.info("Shutting down directory server ..."); this.logger.info("Shutting down directory server ...");
try { try {
this.server.stop(); this.server.stop();
@ -318,9 +289,7 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
this.logger.error("Shutdown failed", ex); this.logger.error("Shutdown failed", ex);
return; return;
} }
this.running = false; this.running = false;
if (this.workingDir.exists()) { if (this.workingDir.exists()) {
this.logger.info("Deleting working directory " + this.workingDir.getAbsolutePath()); this.logger.info("Deleting working directory " + this.workingDir.getAbsolutePath());
deleteDir(this.workingDir); deleteDir(this.workingDir);
@ -329,43 +298,31 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
private void importLdifs() throws Exception { private void importLdifs() throws Exception {
// Import any ldif files // Import any ldif files
Resource[] ldifs; Resource[] ldifs = (this.ctxt != null) ? this.ctxt.getResources(this.ldifResources)
: new PathMatchingResourcePatternResolver().getResources(this.ldifResources);
if (this.ctxt == null) {
// Not running within an app context
ldifs = new PathMatchingResourcePatternResolver().getResources(this.ldifResources);
}
else {
ldifs = this.ctxt.getResources(this.ldifResources);
}
// Note that we can't just import using the ServerContext returned // Note that we can't just import using the ServerContext returned
// from starting Apache DS, apparently because of the long-running issue // from starting Apache DS, apparently because of the long-running issue
// DIRSERVER-169. // DIRSERVER-169.
// We need a standard context. // We need a standard context.
// DirContext dirContext = contextSource.getReadWriteContext(); // DirContext dirContext = contextSource.getReadWriteContext();
if (ldifs == null || ldifs.length == 0) { if (ldifs == null || ldifs.length == 0) {
return; return;
} }
Assert.isTrue(ldifs.length == 1, () -> "More than one LDIF resource found with the supplied pattern:"
+ this.ldifResources + " Got " + Arrays.toString(ldifs));
String ldifFile = getLdifFile(ldifs);
this.logger.info("Loading LDIF file: " + ldifFile);
LdifFileLoader loader = new LdifFileLoader(this.service.getAdminSession(), new File(ldifFile), null,
getClass().getClassLoader());
loader.execute();
}
if (ldifs.length == 1) { private String getLdifFile(Resource[] ldifs) throws IOException {
String ldifFile; try {
return ldifs[0].getFile().getAbsolutePath();
try {
ldifFile = ldifs[0].getFile().getAbsolutePath();
}
catch (IOException ex) {
ldifFile = ldifs[0].getURI().toString();
}
this.logger.info("Loading LDIF file: " + ldifFile);
LdifFileLoader loader = new LdifFileLoader(this.service.getAdminSession(), new File(ldifFile), null,
getClass().getClassLoader());
loader.execute();
} }
else { catch (IOException ex) {
throw new IllegalArgumentException("More than one LDIF resource found with the supplied pattern:" return ldifs[0].getURI().toString();
+ this.ldifResources + " Got " + Arrays.toString(ldifs));
} }
} }
@ -373,7 +330,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
String parentTempDir = System.getProperty("java.io.tmpdir"); String parentTempDir = System.getProperty("java.io.tmpdir");
String fileNamePrefix = prefix + System.nanoTime(); String fileNamePrefix = prefix + System.nanoTime();
String fileName = fileNamePrefix; String fileName = fileNamePrefix;
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
File tempDir = new File(parentTempDir, fileName); File tempDir = new File(parentTempDir, fileName);
if (!tempDir.exists()) { if (!tempDir.exists()) {
@ -381,7 +337,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
} }
fileName = fileNamePrefix + "~" + i; fileName = fileNamePrefix + "~" + i;
} }
throw new IOException( throw new IOException(
"Failed to create a temporary directory for file at " + new File(parentTempDir, fileNamePrefix)); "Failed to create a temporary directory for file at " + new File(parentTempDir, fileNamePrefix));
} }
@ -396,7 +351,6 @@ public class ApacheDSContainer implements InitializingBean, DisposableBean, Life
} }
} }
} }
return dir.delete(); return dir.delete();
} }

View File

@ -85,20 +85,16 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
if (isRunning()) { if (isRunning()) {
return; return;
} }
try { try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(this.defaultPartitionSuffix); InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(this.defaultPartitionSuffix);
config.addAdditionalBindCredentials("uid=admin,ou=system", "secret"); config.addAdditionalBindCredentials("uid=admin,ou=system", "secret");
config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.port)); config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.port));
config.setEnforceSingleStructuralObjectClass(false); config.setEnforceSingleStructuralObjectClass(false);
config.setEnforceAttributeSyntaxCompliance(true); config.setEnforceAttributeSyntaxCompliance(true);
DN dn = new DN(this.defaultPartitionSuffix); DN dn = new DN(this.defaultPartitionSuffix);
Entry entry = new Entry(dn); Entry entry = new Entry(dn);
entry.addAttribute("objectClass", "top", "domain", "extensibleObject"); entry.addAttribute("objectClass", "top", "domain", "extensibleObject");
entry.addAttribute("dc", dn.getRDN().getAttributeValues()[0]); entry.addAttribute("dc", dn.getRDN().getAttributeValues()[0]);
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config); InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
directoryServer.add(entry); directoryServer.add(entry);
importLdif(directoryServer); importLdif(directoryServer);
@ -110,7 +106,6 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
catch (LDAPException ex) { catch (LDAPException ex) {
throw new RuntimeException("Server startup failed", ex); throw new RuntimeException("Server startup failed", ex);
} }
} }
private void importLdif(InMemoryDirectoryServer directoryServer) { private void importLdif(InMemoryDirectoryServer directoryServer) {

View File

@ -29,6 +29,7 @@ import javax.naming.directory.SearchControls;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.LdapTemplate;
@ -161,21 +162,17 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
this.ldapTemplate = new SpringSecurityLdapTemplate(contextSource); this.ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
getLdapTemplate().setSearchControls(getSearchControls()); getLdapTemplate().setSearchControls(getSearchControls());
this.groupSearchBase = groupSearchBase; this.groupSearchBase = groupSearchBase;
if (groupSearchBase == null) { if (groupSearchBase == null) {
logger.info("groupSearchBase is null. No group search will be performed."); logger.info("groupSearchBase is null. No group search will be performed.");
} }
else if (groupSearchBase.length() == 0) { else if (groupSearchBase.length() == 0) {
logger.info("groupSearchBase is empty. Searches will be performed from the context source base"); logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
} }
this.authorityMapper = (record) -> { this.authorityMapper = (record) -> {
String role = record.get(this.groupRoleAttribute).get(0); String role = record.get(this.groupRoleAttribute).get(0);
if (this.convertToUpperCase) { if (this.convertToUpperCase) {
role = role.toUpperCase(); role = role.toUpperCase();
} }
return new SimpleGrantedAuthority(this.rolePrefix + role); return new SimpleGrantedAuthority(this.rolePrefix + role);
}; };
} }
@ -202,26 +199,17 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
@Override @Override
public final Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username) { public final Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username) {
String userDn = user.getNameInNamespace(); String userDn = user.getNameInNamespace();
logger.debug(LogMessage.format("Getting authorities for user %s", userDn));
if (logger.isDebugEnabled()) {
logger.debug("Getting authorities for user " + userDn);
}
Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username); Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username);
Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username); Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username);
if (extraRoles != null) { if (extraRoles != null) {
roles.addAll(extraRoles); roles.addAll(extraRoles);
} }
if (this.defaultRole != null) { if (this.defaultRole != null) {
roles.add(this.defaultRole); roles.add(this.defaultRole);
} }
List<GrantedAuthority> result = new ArrayList<>(roles.size()); List<GrantedAuthority> result = new ArrayList<>(roles.size());
result.addAll(roles); result.addAll(roles);
return result; return result;
} }
@ -229,26 +217,16 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
if (getGroupSearchBase() == null) { if (getGroupSearchBase() == null) {
return new HashSet<>(); return new HashSet<>();
} }
Set<GrantedAuthority> authorities = new HashSet<>(); Set<GrantedAuthority> authorities = new HashSet<>();
logger.debug(LogMessage.of(() -> "Searching for roles for user '" + username + "', DN = " + "'" + userDn
if (logger.isDebugEnabled()) { + "', with filter " + this.groupSearchFilter + " in search base '" + getGroupSearchBase() + "'"));
logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
+ this.groupSearchFilter + " in search base '" + getGroupSearchBase() + "'");
}
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues( Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
getGroupSearchBase(), this.groupSearchFilter, new String[] { userDn, username }, getGroupSearchBase(), this.groupSearchFilter, new String[] { userDn, username },
new String[] { this.groupRoleAttribute }); new String[] { this.groupRoleAttribute });
logger.debug(LogMessage.of(() -> "Roles from search: " + userRoles));
if (logger.isDebugEnabled()) {
logger.debug("Roles from search: " + userRoles);
}
for (Map<String, List<String>> role : userRoles) { for (Map<String, List<String>> role : userRoles) {
authorities.add(this.authorityMapper.apply(role)); authorities.add(this.authorityMapper.apply(role));
} }
return authorities; return authorities;
} }

View File

@ -27,7 +27,7 @@ import org.springframework.security.core.SpringSecurityCoreVersion;
* <p> * <p>
* The username will be mapped from the <tt>uid</tt> attribute by default. * The username will be mapped from the <tt>uid</tt> attribute by default.
* *
* @author Luke * @author Luke Taylor
*/ */
public class InetOrgPerson extends Person { public class InetOrgPerson extends Person {

View File

@ -33,10 +33,8 @@ public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) { Collection<? extends GrantedAuthority> authorities) {
InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx); InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx);
p.setUsername(username); p.setUsername(username);
p.setAuthorities(authorities); p.setAuthorities(authorities);
return p.createUserDetails(); return p.createUserDetails();
} }
@ -44,7 +42,6 @@ public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
@Override @Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(InetOrgPerson.class, user, "UserDetails must be an InetOrgPerson instance"); Assert.isInstanceOf(InetOrgPerson.class, user, "UserDetails must be an InetOrgPerson instance");
InetOrgPerson p = (InetOrgPerson) user; InetOrgPerson p = (InetOrgPerson) user;
p.populateContext(ctx); p.populateContext(ctx);
} }

View File

@ -55,7 +55,6 @@ public class LdapAuthority implements GrantedAuthority {
public LdapAuthority(String role, String dn, Map<String, List<String>> attributes) { public LdapAuthority(String role, String dn, Map<String, List<String>> attributes) {
Assert.notNull(role, "role can not be null"); Assert.notNull(role, "role can not be null");
Assert.notNull(dn, "dn can not be null"); Assert.notNull(dn, "dn can not be null");
this.role = role; this.role = role;
this.dn = dn; this.dn = dn;
this.attributes = attributes; this.attributes = attributes;
@ -87,10 +86,7 @@ public class LdapAuthority implements GrantedAuthority {
if (this.attributes != null) { if (this.attributes != null) {
result = this.attributes.get(name); result = this.attributes.get(name);
} }
if (result == null) { return (result != null) ? result : Collections.emptyList();
result = Collections.emptyList();
}
return result;
} }
/** /**
@ -100,17 +96,9 @@ public class LdapAuthority implements GrantedAuthority {
*/ */
public String getFirstAttributeValue(String name) { public String getFirstAttributeValue(String name) {
List<String> result = getAttributeValues(name); List<String> result = getAttributeValues(name);
if (result.isEmpty()) { return (!result.isEmpty()) ? result.get(0) : null;
return null;
}
else {
return result.get(0);
}
} }
/**
* {@inheritDoc}
*/
@Override @Override
public String getAuthority() { public String getAuthority() {
return this.role; return this.role;
@ -118,23 +106,21 @@ public class LdapAuthority implements GrantedAuthority {
/** /**
* Compares the LdapAuthority based on {@link #getAuthority()} and {@link #getDn()} * Compares the LdapAuthority based on {@link #getAuthority()} and {@link #getDn()}
* values {@inheritDoc} * values.
*/ */
@Override @Override
public boolean equals(Object o) { public boolean equals(Object obj) {
if (this == o) { if (this == obj) {
return true; return true;
} }
if (!(o instanceof LdapAuthority)) { if (!(obj instanceof LdapAuthority)) {
return false; return false;
} }
LdapAuthority other = (LdapAuthority) obj;
LdapAuthority that = (LdapAuthority) o; if (!this.dn.equals(other.dn)) {
if (!this.dn.equals(that.dn)) {
return false; return false;
} }
return this.role.equals(that.role); return this.role.equals(other.role);
} }
@Override @Override

View File

@ -154,11 +154,9 @@ public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; "); sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
sb.append("CredentialsNonExpired: ").append(this.credentialsNonExpired).append("; "); sb.append("CredentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; "); sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
if (this.getAuthorities() != null && !this.getAuthorities().isEmpty()) { if (this.getAuthorities() != null && !this.getAuthorities().isEmpty()) {
sb.append("Granted Authorities: "); sb.append("Granted Authorities: ");
boolean first = true; boolean first = true;
for (Object authority : this.getAuthorities()) { for (Object authority : this.getAuthorities()) {
if (first) { if (first) {
first = false; first = false;
@ -166,14 +164,12 @@ public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData
else { else {
sb.append(", "); sb.append(", ");
} }
sb.append(authority.toString()); sb.append(authority.toString());
} }
} }
else { else {
sb.append("Not granted any authorities"); sb.append("Not granted any authorities");
} }
return sb.toString(); return sb.toString();
} }
@ -231,13 +227,9 @@ public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData
Assert.notNull(this.instance, "Essence can only be used to create a single instance"); Assert.notNull(this.instance, "Essence can only be used to create a single instance");
Assert.notNull(this.instance.username, "username must not be null"); Assert.notNull(this.instance.username, "username must not be null");
Assert.notNull(this.instance.getDn(), "Distinguished name must not be null"); Assert.notNull(this.instance.getDn(), "Distinguished name must not be null");
this.instance.authorities = Collections.unmodifiableList(this.mutableAuthorities); this.instance.authorities = Collections.unmodifiableList(this.mutableAuthorities);
LdapUserDetails newInstance = this.instance; LdapUserDetails newInstance = this.instance;
this.instance = null; this.instance = null;
return newInstance; return newInstance;
} }

View File

@ -40,6 +40,7 @@ import javax.naming.ldap.LdapContext;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.core.AttributesMapper; import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.AttributesMapperCallbackHandler; import org.springframework.ldap.core.AttributesMapperCallbackHandler;
import org.springframework.ldap.core.ContextExecutor; import org.springframework.ldap.core.ContextExecutor;
@ -116,12 +117,9 @@ public class LdapUserDetailsManager implements UserDetailsManager {
/** Default context mapper used to create a set of roles from a list of attributes */ /** 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); Attribute roleAttr = attributes.get(this.groupRoleAttributeName);
NamingEnumeration<?> ne = roleAttr.getAll(); NamingEnumeration<?> ne = roleAttr.getAll();
// assert ne.hasMore();
Object group = ne.next(); Object group = ne.next();
String role = group.toString(); String role = group.toString();
return new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase()); return new SimpleGrantedAuthority(this.rolePrefix + role.toUpperCase());
}; };
@ -137,11 +135,8 @@ public class LdapUserDetailsManager implements UserDetailsManager {
public UserDetails loadUserByUsername(String username) { public UserDetails loadUserByUsername(String username) {
DistinguishedName dn = this.usernameMapper.buildDn(username); DistinguishedName dn = this.usernameMapper.buildDn(username);
List<GrantedAuthority> authorities = getUserAuthorities(dn, username); List<GrantedAuthority> authorities = getUserAuthorities(dn, username);
this.logger.debug(LogMessage.format("Loading user '%s' with DN '%s'", username, dn));
this.logger.debug("Loading user '" + username + "' with DN '" + dn + "'");
DirContextAdapter userCtx = loadUserAsContext(dn, username); DirContextAdapter userCtx = loadUserAsContext(dn, username);
return this.userDetailsMapper.mapUserFromContext(userCtx, username, authorities); return this.userDetailsMapper.mapUserFromContext(userCtx, username, authorities);
} }
@ -151,8 +146,8 @@ public class LdapUserDetailsManager implements UserDetailsManager {
Attributes attrs = ctx.getAttributes(dn, this.attributesToRetrieve); Attributes attrs = ctx.getAttributes(dn, this.attributesToRetrieve);
return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx)); return new DirContextAdapter(attrs, LdapUtils.getFullDn(dn, ctx));
} }
catch (NameNotFoundException notFound) { catch (NameNotFoundException ex) {
throw new UsernameNotFoundException("User " + username + " not found", notFound); throw new UsernameNotFoundException("User " + username + " not found", ex);
} }
}); });
} }
@ -187,13 +182,9 @@ public class LdapUserDetailsManager implements UserDetailsManager {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Assert.notNull(authentication, Assert.notNull(authentication,
"No authentication object found in security context. Can't change current user's password!"); "No authentication object found in security context. Can't change current user's password!");
String username = authentication.getName(); String username = authentication.getName();
this.logger.debug(LogMessage.format("Changing password for user '%s'", username));
this.logger.debug("Changing password for user '" + username);
DistinguishedName userDn = this.usernameMapper.buildDn(username); DistinguishedName userDn = this.usernameMapper.buildDn(username);
if (this.usePasswordModifyExtensionOperation) { if (this.usePasswordModifyExtensionOperation) {
changePasswordUsingExtensionOperation(userDn, oldPassword, newPassword); changePasswordUsingExtensionOperation(userDn, oldPassword, newPassword);
} }
@ -214,13 +205,10 @@ public class LdapUserDetailsManager implements UserDetailsManager {
DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx); DistinguishedName fullDn = LdapUtils.getFullDn(dn, ctx);
SearchControls ctrls = new SearchControls(); SearchControls ctrls = new SearchControls();
ctrls.setReturningAttributes(new String[] { this.groupRoleAttributeName }); ctrls.setReturningAttributes(new String[] { this.groupRoleAttributeName });
return ctx.search(this.groupSearchBase, this.groupSearchFilter, new String[] { fullDn.toUrl(), username }, return ctx.search(this.groupSearchBase, this.groupSearchFilter, new String[] { fullDn.toUrl(), username },
ctrls); ctrls);
}; };
AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler(this.roleMapper); AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler(this.roleMapper);
this.template.search(se, roleCollector); this.template.search(se, roleCollector);
return roleCollector.getList(); return roleCollector.getList();
} }
@ -230,38 +218,28 @@ public class LdapUserDetailsManager implements UserDetailsManager {
DirContextAdapter ctx = new DirContextAdapter(); DirContextAdapter ctx = new DirContextAdapter();
copyToContext(user, ctx); copyToContext(user, ctx);
DistinguishedName dn = this.usernameMapper.buildDn(user.getUsername()); DistinguishedName dn = this.usernameMapper.buildDn(user.getUsername());
this.logger.debug(LogMessage.format("Creating new user '%s' with DN '%s'", user.getUsername(), dn));
this.logger.debug("Creating new user '" + user.getUsername() + "' with DN '" + dn + "'");
this.template.bind(dn, ctx, null); this.template.bind(dn, ctx, null);
// Check for any existing authorities which might be set for this
// Check for any existing authorities which might be set for this DN and remove // DN and remove them
// them
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername()); List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
if (authorities.size() > 0) { if (authorities.size() > 0) {
removeAuthorities(dn, authorities); removeAuthorities(dn, authorities);
} }
addAuthorities(dn, user.getAuthorities()); addAuthorities(dn, user.getAuthorities());
} }
@Override @Override
public void updateUser(UserDetails user) { public void updateUser(UserDetails user) {
DistinguishedName dn = this.usernameMapper.buildDn(user.getUsername()); DistinguishedName dn = this.usernameMapper.buildDn(user.getUsername());
this.logger.debug(LogMessage.format("Updating new user '%s' with DN '%s'", user.getUsername(), dn));
this.logger.debug("Updating user '" + user.getUsername() + "' with DN '" + dn + "'");
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername()); List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername()); DirContextAdapter ctx = loadUserAsContext(dn, user.getUsername());
ctx.setUpdateMode(true); ctx.setUpdateMode(true);
copyToContext(user, ctx); copyToContext(user, ctx);
// Remove the objectclass attribute from the list of mods (if present). // Remove the objectclass attribute from the list of mods (if present).
List<ModificationItem> mods = new LinkedList<>(Arrays.asList(ctx.getModificationItems())); List<ModificationItem> mods = new LinkedList<>(Arrays.asList(ctx.getModificationItems()));
ListIterator<ModificationItem> modIt = mods.listIterator(); ListIterator<ModificationItem> modIt = mods.listIterator();
while (modIt.hasNext()) { while (modIt.hasNext()) {
ModificationItem mod = modIt.next(); ModificationItem mod = modIt.next();
Attribute a = mod.getAttribute(); Attribute a = mod.getAttribute();
@ -269,9 +247,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
modIt.remove(); modIt.remove();
} }
} }
this.template.modifyAttributes(dn, mods.toArray(new ModificationItem[0])); this.template.modifyAttributes(dn, mods.toArray(new ModificationItem[0]));
// template.rebind(dn, ctx, null); // template.rebind(dn, ctx, null);
// Remove the old authorities and replace them with the new one // Remove the old authorities and replace them with the new one
removeAuthorities(dn, authorities); removeAuthorities(dn, authorities);
@ -288,7 +264,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
@Override @Override
public boolean userExists(String username) { public boolean userExists(String username) {
DistinguishedName dn = this.usernameMapper.buildDn(username); DistinguishedName dn = this.usernameMapper.buildDn(username);
try { try {
Object obj = this.template.lookup(dn); Object obj = this.template.lookup(dn);
if (obj instanceof Context) { if (obj instanceof Context) {
@ -309,7 +284,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
protected DistinguishedName buildGroupDn(String group) { protected DistinguishedName buildGroupDn(String group) {
DistinguishedName dn = new DistinguishedName(this.groupSearchBase); DistinguishedName dn = new DistinguishedName(this.groupSearchBase);
dn.add(this.groupRoleAttributeName, group.toLowerCase()); dn.add(this.groupRoleAttributeName, group.toLowerCase());
return dn; return dn;
} }
@ -333,7 +307,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx); DistinguishedName fullDn = LdapUtils.getFullDn(userDn, ctx);
ModificationItem addGroup = new ModificationItem(modType, ModificationItem addGroup = new ModificationItem(modType,
new BasicAttribute(this.groupMemberAttributeName, fullDn.toUrl())); new BasicAttribute(this.groupMemberAttributeName, fullDn.toUrl()));
ctx.modifyAttributes(buildGroupDn(group), new ModificationItem[] { addGroup }); ctx.modifyAttributes(buildGroupDn(group), new ModificationItem[] { addGroup });
} }
return null; return null;
@ -342,11 +315,9 @@ public class LdapUserDetailsManager implements UserDetailsManager {
private String convertAuthorityToGroup(GrantedAuthority authority) { private String convertAuthorityToGroup(GrantedAuthority authority) {
String group = authority.getAuthority(); String group = authority.getAuthority();
if (group.startsWith(this.rolePrefix)) { if (group.startsWith(this.rolePrefix)) {
group = group.substring(this.rolePrefix.length()); group = group.substring(this.rolePrefix.length());
} }
return group; return group;
} }
@ -419,15 +390,12 @@ public class LdapUserDetailsManager implements UserDetailsManager {
private void changePasswordUsingAttributeModification(DistinguishedName userDn, String oldPassword, private void changePasswordUsingAttributeModification(DistinguishedName userDn, String oldPassword,
String newPassword) { String newPassword) {
ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
final ModificationItem[] passwordChange = new ModificationItem[] { new ModificationItem( new BasicAttribute(this.passwordAttributeName, newPassword)) };
DirContext.REPLACE_ATTRIBUTE, new BasicAttribute(this.passwordAttributeName, newPassword)) };
if (oldPassword == null) { if (oldPassword == null) {
this.template.modifyAttributes(userDn, passwordChange); this.template.modifyAttributes(userDn, passwordChange);
return; return;
} }
this.template.executeReadWrite((dirCtx) -> { this.template.executeReadWrite((dirCtx) -> {
LdapContext ctx = (LdapContext) dirCtx; LdapContext ctx = (LdapContext) dirCtx;
ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool"); ctx.removeFromEnvironment("com.sun.jndi.ldap.connect.pool");
@ -440,23 +408,17 @@ public class LdapUserDetailsManager implements UserDetailsManager {
catch (javax.naming.AuthenticationException ex) { catch (javax.naming.AuthenticationException ex) {
throw new BadCredentialsException("Authentication for password change failed."); throw new BadCredentialsException("Authentication for password change failed.");
} }
ctx.modifyAttributes(userDn, passwordChange); ctx.modifyAttributes(userDn, passwordChange);
return null; return null;
}); });
} }
private void changePasswordUsingExtensionOperation(DistinguishedName userDn, String oldPassword, private void changePasswordUsingExtensionOperation(DistinguishedName userDn, String oldPassword,
String newPassword) { String newPassword) {
this.template.executeReadWrite((dirCtx) -> { this.template.executeReadWrite((dirCtx) -> {
LdapContext ctx = (LdapContext) dirCtx; LdapContext ctx = (LdapContext) dirCtx;
String userIdentity = LdapUtils.getFullDn(userDn, ctx).encode(); String userIdentity = LdapUtils.getFullDn(userDn, ctx).encode();
PasswordModifyRequest request = new PasswordModifyRequest(userIdentity, oldPassword, newPassword); PasswordModifyRequest request = new PasswordModifyRequest(userIdentity, oldPassword, newPassword);
try { try {
return ctx.extendedOperation(request); return ctx.extendedOperation(request);
} }
@ -493,19 +455,15 @@ public class LdapUserDetailsManager implements UserDetailsManager {
PasswordModifyRequest(String userIdentity, String oldPassword, String newPassword) { PasswordModifyRequest(String userIdentity, String oldPassword, String newPassword) {
ByteArrayOutputStream elements = new ByteArrayOutputStream(); ByteArrayOutputStream elements = new ByteArrayOutputStream();
if (userIdentity != null) { if (userIdentity != null) {
berEncode(USER_IDENTITY_OCTET_TYPE, userIdentity.getBytes(), elements); berEncode(USER_IDENTITY_OCTET_TYPE, userIdentity.getBytes(), elements);
} }
if (oldPassword != null) { if (oldPassword != null) {
berEncode(OLD_PASSWORD_OCTET_TYPE, oldPassword.getBytes(), elements); berEncode(OLD_PASSWORD_OCTET_TYPE, oldPassword.getBytes(), elements);
} }
if (newPassword != null) { if (newPassword != null) {
berEncode(NEW_PASSWORD_OCTET_TYPE, newPassword.getBytes(), elements); berEncode(NEW_PASSWORD_OCTET_TYPE, newPassword.getBytes(), elements);
} }
berEncode(SEQUENCE_TYPE, elements.toByteArray(), this.value); berEncode(SEQUENCE_TYPE, elements.toByteArray(), this.value);
} }
@ -532,9 +490,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
*/ */
private void berEncode(byte type, byte[] src, ByteArrayOutputStream dest) { private void berEncode(byte type, byte[] src, ByteArrayOutputStream dest) {
int length = src.length; int length = src.length;
dest.write(type); dest.write(type);
if (length < 128) { if (length < 128) {
dest.write(length); dest.write(length);
} }
@ -560,7 +516,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
dest.write((byte) ((length >> 8) & 0xFF)); dest.write((byte) ((length >> 8) & 0xFF));
dest.write((byte) (length & 0xFF)); dest.write((byte) (length & 0xFF));
} }
try { try {
dest.write(src); dest.write(src);
} }

View File

@ -21,6 +21,7 @@ import java.util.Collection;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.core.DirContextAdapter; import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
@ -53,56 +54,41 @@ public class LdapUserDetailsMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) { Collection<? extends GrantedAuthority> authorities) {
String dn = ctx.getNameInNamespace(); String dn = ctx.getNameInNamespace();
this.logger.debug(LogMessage.format("Mapping user details from context with DN: %s", dn));
this.logger.debug("Mapping user details from context with DN: " + dn);
LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence(); LdapUserDetailsImpl.Essence essence = new LdapUserDetailsImpl.Essence();
essence.setDn(dn); essence.setDn(dn);
Object passwordValue = ctx.getObjectAttribute(this.passwordAttributeName); Object passwordValue = ctx.getObjectAttribute(this.passwordAttributeName);
if (passwordValue != null) { if (passwordValue != null) {
essence.setPassword(mapPassword(passwordValue)); essence.setPassword(mapPassword(passwordValue));
} }
essence.setUsername(username); essence.setUsername(username);
// Map the roles // Map the roles
for (int i = 0; (this.roleAttributes != null) && (i < this.roleAttributes.length); i++) { for (int i = 0; (this.roleAttributes != null) && (i < this.roleAttributes.length); i++) {
String[] rolesForAttribute = ctx.getStringAttributes(this.roleAttributes[i]); String[] rolesForAttribute = ctx.getStringAttributes(this.roleAttributes[i]);
if (rolesForAttribute == null) { if (rolesForAttribute == null) {
this.logger.debug("Couldn't read role attribute '" + this.roleAttributes[i] + "' for user " + dn); this.logger.debug(
LogMessage.format("Couldn't read role attribute '%s' for user $s", this.roleAttributes[i], dn));
continue; continue;
} }
for (String role : rolesForAttribute) { for (String role : rolesForAttribute) {
GrantedAuthority authority = createAuthority(role); GrantedAuthority authority = createAuthority(role);
if (authority != null) { if (authority != null) {
essence.addAuthority(authority); essence.addAuthority(authority);
} }
} }
} }
// Add the supplied authorities // Add the supplied authorities
for (GrantedAuthority authority : authorities) { for (GrantedAuthority authority : authorities) {
essence.addAuthority(authority); essence.addAuthority(authority);
} }
// Check for PPolicy data // Check for PPolicy data
PasswordPolicyResponseControl ppolicy = (PasswordPolicyResponseControl) ctx PasswordPolicyResponseControl ppolicy = (PasswordPolicyResponseControl) ctx
.getObjectAttribute(PasswordPolicyControl.OID); .getObjectAttribute(PasswordPolicyControl.OID);
if (ppolicy != null) { if (ppolicy != null) {
essence.setTimeBeforeExpiration(ppolicy.getTimeBeforeExpiration()); essence.setTimeBeforeExpiration(ppolicy.getTimeBeforeExpiration());
essence.setGraceLoginsRemaining(ppolicy.getGraceLoginsRemaining()); essence.setGraceLoginsRemaining(ppolicy.getGraceLoginsRemaining());
} }
return essence.createUserDetails(); return essence.createUserDetails();
} }
@Override @Override
@ -118,12 +104,10 @@ public class LdapUserDetailsMapper implements UserDetailsContextMapper {
* @return a String representation of the password. * @return a String representation of the password.
*/ */
protected String mapPassword(Object passwordValue) { protected String mapPassword(Object passwordValue) {
if (!(passwordValue instanceof String)) { if (!(passwordValue instanceof String)) {
// Assume it's binary // Assume it's binary
passwordValue = new String((byte[]) passwordValue); passwordValue = new String((byte[]) passwordValue);
} }
return (String) passwordValue; return (String) passwordValue;
} }

View File

@ -57,7 +57,6 @@ public class LdapUserDetailsService implements UserDetailsService {
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DirContextOperations userData = this.userSearch.searchForUser(username); DirContextOperations userData = this.userSearch.searchForUser(username);
return this.userDetailsMapper.mapUserFromContext(userData, username, return this.userDetailsMapper.mapUserFromContext(userData, username,
this.authoritiesPopulator.getGrantedAuthorities(userData, username)); this.authoritiesPopulator.getGrantedAuthorities(userData, username));
} }

View File

@ -24,6 +24,7 @@ import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.core.ContextSource; import org.springframework.ldap.core.ContextSource;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.ldap.SpringSecurityLdapTemplate; import org.springframework.security.ldap.SpringSecurityLdapTemplate;
@ -144,19 +145,13 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
super(contextSource, groupSearchBase); super(contextSource, groupSearchBase);
} }
/**
* {@inheritDoc}
*/
@Override @Override
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) { public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
if (getGroupSearchBase() == null) { if (getGroupSearchBase() == null) {
return new HashSet<>(); return new HashSet<>();
} }
Set<GrantedAuthority> authorities = new HashSet<>(); Set<GrantedAuthority> authorities = new HashSet<>();
performNestedSearch(userDn, username, authorities, getMaxSearchDepth()); performNestedSearch(userDn, username, authorities, getMaxSearchDepth());
return authorities; return authorities;
} }
@ -171,34 +166,23 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
private void performNestedSearch(String userDn, String username, Set<GrantedAuthority> authorities, int depth) { private void performNestedSearch(String userDn, String username, Set<GrantedAuthority> authorities, int depth) {
if (depth == 0) { if (depth == 0) {
// back out of recursion // back out of recursion
if (logger.isDebugEnabled()) { logger.debug(LogMessage.of(() -> "Search aborted, max depth reached," + " for roles for user '" + username
logger.debug("Search aborted, max depth reached," + " for roles for user '" + username + "', DN = " + "', DN = " + "'" + userDn + "', with filter " + getGroupSearchFilter() + " in search base '"
+ "'" + userDn + "', with filter " + getGroupSearchFilter() + " in search base '" + getGroupSearchBase() + "'"));
+ getGroupSearchBase() + "'");
}
return; return;
} }
logger.debug(LogMessage.of(() -> "Searching for roles for user '" + username + "', DN = " + "'" + userDn
if (logger.isDebugEnabled()) { + "', with filter " + getGroupSearchFilter() + " in search base '" + getGroupSearchBase() + "'"));
logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
+ getGroupSearchFilter() + " in search base '" + getGroupSearchBase() + "'");
}
if (getAttributeNames() == null) { if (getAttributeNames() == null) {
setAttributeNames(new HashSet<>()); setAttributeNames(new HashSet<>());
} }
if (StringUtils.hasText(getGroupRoleAttribute()) && !getAttributeNames().contains(getGroupRoleAttribute())) { if (StringUtils.hasText(getGroupRoleAttribute()) && !getAttributeNames().contains(getGroupRoleAttribute())) {
getAttributeNames().add(getGroupRoleAttribute()); getAttributeNames().add(getGroupRoleAttribute());
} }
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues( Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
getGroupSearchBase(), getGroupSearchFilter(), new String[] { userDn, username }, getGroupSearchBase(), getGroupSearchFilter(), new String[] { userDn, username },
getAttributeNames().toArray(new String[0])); getAttributeNames().toArray(new String[0]));
logger.debug(LogMessage.format("Roles from search: %s", userRoles));
if (logger.isDebugEnabled()) {
logger.debug("Roles from search: " + userRoles);
}
for (Map<String, List<String>> record : userRoles) { for (Map<String, List<String>> record : userRoles) {
boolean circular = false; boolean circular = false;
String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0); String dn = record.get(SpringSecurityLdapTemplate.DN_KEY).get(0);
@ -220,7 +204,6 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
if (!circular) { if (!circular) {
performNestedSearch(dn, roleName, authorities, (depth - 1)); performNestedSearch(dn, roleName, authorities, (depth - 1));
} }
} }
} }

View File

@ -76,7 +76,6 @@ public class Person extends LdapUserDetailsImpl {
adapter.setAttributeValues("cn", getCn()); adapter.setAttributeValues("cn", getCn());
adapter.setAttributeValue("description", getDescription()); adapter.setAttributeValue("description", getDescription());
adapter.setAttributeValue("telephoneNumber", getTelephoneNumber()); adapter.setAttributeValue("telephoneNumber", getTelephoneNumber());
if (getPassword() != null) { if (getPassword() != null) {
adapter.setAttributeValue("userPassword", getPassword()); adapter.setAttributeValue("userPassword", getPassword());
} }
@ -95,11 +94,9 @@ public class Person extends LdapUserDetailsImpl {
setSn(ctx.getStringAttribute("sn")); setSn(ctx.getStringAttribute("sn"));
setDescription(ctx.getStringAttribute("description")); setDescription(ctx.getStringAttribute("description"));
setTelephoneNumber(ctx.getStringAttribute("telephoneNumber")); setTelephoneNumber(ctx.getStringAttribute("telephoneNumber"));
Object passo = ctx.getObjectAttribute("userPassword"); Object password = ctx.getObjectAttribute("userPassword");
if (password != null) {
if (passo != null) { setPassword(LdapUtils.convertPasswordToString(password));
String password = LdapUtils.convertPasswordToString(passo);
setPassword(password);
} }
} }

View File

@ -33,18 +33,14 @@ public class PersonContextMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) { Collection<? extends GrantedAuthority> authorities) {
Person.Essence p = new Person.Essence(ctx); Person.Essence p = new Person.Essence(ctx);
p.setUsername(username); p.setUsername(username);
p.setAuthorities(authorities); p.setAuthorities(authorities);
return p.createUserDetails(); return p.createUserDetails();
} }
@Override @Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(Person.class, user, "UserDetails must be a Person instance"); Assert.isInstanceOf(Person.class, user, "UserDetails must be a Person instance");
Person p = (Person) user; Person p = (Person) user;
p.populateContext(ctx); p.populateContext(ctx);
} }