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
public DistinguishedName buildDn(String username) {
DistinguishedName dn = new DistinguishedName(this.userDnBase);
dn.add(this.usernameAttribute, username);
return dn;
}

View File

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

View File

@ -34,18 +34,11 @@ final class LdapEncoder {
private static final int HEX = 16;
private static String[] NAME_ESCAPE_TABLE = new String[96];
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static {
// Name encoding table -------------------------------------
// all below 0x20 (control chars)
for (char c = 0; c < ' '; c++) {
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
}
NAME_ESCAPE_TABLE['#'] = "\\#";
NAME_ESCAPE_TABLE[','] = "\\,";
NAME_ESCAPE_TABLE[';'] = "\\;";
@ -55,21 +48,21 @@ final class LdapEncoder {
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
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
}
// escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00";
}
/**
@ -79,15 +72,8 @@ final class LdapEncoder {
}
protected static String toTwoCharHex(char c) {
String raw = Integer.toHexString(c).toUpperCase();
if (raw.length() > 1) {
return raw;
}
else {
return "0" + raw;
}
return (raw.length() > 1) ? raw : "0" + raw;
}
/**
@ -96,29 +82,15 @@ final class LdapEncoder {
* @return a properly escaped representation of the supplied value.
*/
static String filterEncode(String value) {
if (value == null) {
return null;
}
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
}
else {
// default: add the char
encodedValue.append(c);
}
char ch = value.charAt(i);
encodedValue.append((ch < FILTER_ESCAPE_TABLE.length) ? FILTER_ESCAPE_TABLE[ch] : ch);
}
return encodedValue.toString();
}
@ -141,43 +113,31 @@ final class LdapEncoder {
* @return The escaped value.
*/
static String nameEncode(String value) {
if (value == null) {
return null;
}
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
int last = length - 1;
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
// space first or last
if (c == ' ' && (i == 0 || i == last)) {
encodedValue.append("\\ ");
continue;
}
// check in table for escapes
if (c < NAME_ESCAPE_TABLE.length) {
// check in table for escapes
String esc = NAME_ESCAPE_TABLE[c];
if (esc != null) {
encodedValue.append(esc);
continue;
}
}
// default: add the char
encodedValue.append(c);
}
return encodedValue.toString();
}
/**
@ -188,43 +148,32 @@ final class LdapEncoder {
* @throws BadLdapGrammarException
*/
static String nameDecode(String value) throws BadLdapGrammarException {
if (value == null) {
return null;
}
// make buffer same size
StringBuilder decoded = new StringBuilder(value.length());
int i = 0;
while (i < value.length()) {
char currentChar = value.charAt(i);
if (currentChar == '\\') {
// Ending with a single backslash is not allowed
if (value.length() <= i + 1) {
// Ending with a single backslash is not allowed
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
}
char nextChar = value.charAt(i + 1);
if (isNormalBackslashEscape(nextChar)) {
decoded.append(nextChar);
i += 2;
}
else {
char nextChar = value.charAt(i + 1);
if (nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || 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;
}
if (value.length() <= i + 2) {
throw new BadLdapGrammarException(
"Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
}
// This should be a hex value
String hexString = "" + nextChar + value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString, HEX));
i += 3;
}
}
else {
@ -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) {
return;
}
try {
if (ctx != null) {
ctx.close();
@ -81,24 +80,17 @@ public final class LdapUtils {
* @throws NamingException any exceptions thrown by the context are propagated.
*/
public static String getRelativeName(String fullDn, Context baseCtx) throws NamingException {
String baseDn = baseCtx.getNameInNamespace();
if (baseDn.length() == 0) {
return fullDn;
}
DistinguishedName base = new DistinguishedName(baseDn);
DistinguishedName full = new DistinguishedName(fullDn);
if (base.equals(full)) {
return "";
}
Assert.isTrue(full.startsWith(base), "Full DN does not start with base DN");
full.removeFirst(base);
return full.toString();
}
@ -108,28 +100,22 @@ public final class LdapUtils {
*/
public static DistinguishedName getFullDn(DistinguishedName dn, Context baseCtx) throws NamingException {
DistinguishedName baseDn = new DistinguishedName(baseCtx.getNameInNamespace());
if (dn.contains(baseDn)) {
return dn;
}
baseDn.append(dn);
return baseDn;
}
public static String convertPasswordToString(Object passObj) {
Assert.notNull(passObj, "Password object to convert must not be null");
if (passObj instanceof byte[]) {
return Utf8.decode((byte[]) passObj);
}
else if (passObj instanceof String) {
if (passObj instanceof String) {
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) {
Assert.hasLength(url, "url must have length");
String urlRootDn;
if (url.startsWith("ldap:") || url.startsWith("ldaps:")) {
URI uri = parseLdapUrl(url);
urlRootDn = uri.getRawPath();
@ -154,11 +138,9 @@ public final class LdapUtils {
// Assume it's an embedded server
urlRootDn = url;
}
if (urlRootDn.startsWith("/")) {
urlRootDn = urlRootDn.substring(1);
}
return urlRootDn;
}
@ -173,14 +155,11 @@ public final class LdapUtils {
private static URI parseLdapUrl(String url) {
Assert.hasLength(url, "url must have length");
try {
return new URI(url);
}
catch (URISyntaxException ex) {
IllegalArgumentException iae = new IllegalArgumentException("Unable to parse url: " + url);
iae.initCause(ex);
throw iae;
throw new IllegalArgumentException("Unable to parse url: " + url, ex);
}
}

View File

@ -37,6 +37,7 @@ import javax.naming.directory.SearchResult;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.ContextExecutor;
import org.springframework.ldap.core.ContextMapper;
@ -46,6 +47,7 @@ import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* 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) {
Assert.notNull(contextSource, "ContextSource cannot be null");
setContextSource(contextSource);
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
* @return true if the supplied value matches that in the directory
*/
public boolean compare(final String dn, final String attributeName, final Object value) {
final String comparisonFilter = "(" + attributeName + "={0})";
class LdapCompareCallback implements ContextExecutor {
@Override
public Object executeWithContext(DirContext ctx) throws NamingException {
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(NO_ATTRS);
ctls.setSearchScope(SearchControls.OBJECT_SCOPE);
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;
public boolean compare(String dn, String attributeName, Object value) {
String comparisonFilter = "(" + attributeName + "={0})";
return executeReadOnly((ctx) -> {
SearchControls searchControls = new SearchControls();
searchControls.setReturningAttributes(NO_ATTRS);
searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
Object[] params = new Object[] { value };
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, params, searchControls);
Boolean match = results.hasMore();
LdapUtils.closeEnumeration(results);
return match;
});
}
/**
@ -123,12 +111,8 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
* @return the object created by the mapper
*/
public DirContextOperations retrieveEntry(final String dn, final String[] attributesToRetrieve) {
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> {
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
// Object object = ctx.lookup(LdapUtils.getRelativeName(dn, ctx));
return new DirContextAdapter(attrs, new DistinguishedName(dn),
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
* 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,
final Object[] params, final String[] attributeNames) {
public Set<Map<String, List<String>>> searchForMultipleAttributeValues(String base, String filter, Object[] params,
String[] attributeNames) {
// Escape the params acording to RFC2254
Object[] encodedParams = new String[params.length];
for (int i = 0; i < params.length; i++) {
encodedParams[i] = LdapEncoder.filterEncode(params[i].toString());
}
String formattedFilter = MessageFormat.format(filter, encodedParams);
logger.debug("Using filter: " + formattedFilter);
final HashSet<Map<String, List<String>>> set = new HashSet<>();
logger.debug(LogMessage.format("Using filter: %s", formattedFilter));
HashSet<Map<String, List<String>>> result = new HashSet<>();
ContextMapper roleMapper = (ctx) -> {
DirContextAdapter adapter = (DirContextAdapter) ctx;
Map<String, List<String>> record = new HashMap<>();
if (attributeNames == null || attributeNames.length == 0) {
if (ObjectUtils.isEmpty(attributeNames)) {
try {
for (NamingEnumeration ae = adapter.getAttributes().getAll(); ae.hasMore();) {
Attribute attr = (Attribute) ae.next();
for (NamingEnumeration enumeration = adapter.getAttributes().getAll(); enumeration.hasMore();) {
Attribute attr = (Attribute) enumeration.next();
extractStringAttributeValues(adapter, record, attr.getID());
}
}
@ -208,17 +188,14 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
}
}
record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter)));
set.add(record);
result.add(record);
return null;
};
SearchControls ctls = new SearchControls();
ctls.setSearchScope(this.searchControls.getSearchScope());
ctls.setReturningAttributes((attributeNames != null && attributeNames.length > 0) ? attributeNames : null);
search(base, formattedFilter, ctls, roleMapper);
return set;
return result;
}
/**
@ -246,27 +223,23 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
String attributeName) {
Object[] values = adapter.getObjectAttributes(attributeName);
if (values == null || values.length == 0) {
if (logger.isDebugEnabled()) {
logger.debug("No attribute value found for '" + attributeName + "'");
}
logger.debug(LogMessage.format("No attribute value found for '%s'", attributeName));
return;
}
List<String> svalues = new ArrayList<>();
for (Object o : values) {
if (o != null) {
if (String.class.isAssignableFrom(o.getClass())) {
svalues.add((String) o);
List<String> stringValues = new ArrayList<>();
for (Object value : values) {
if (value != null) {
if (String.class.isAssignableFrom(value.getClass())) {
stringValues.add((String) value);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Attribute:" + attributeName + " contains a non string value of type["
+ o.getClass() + "]");
}
svalues.add(o.toString());
logger.debug(LogMessage.format("Attribute:%s contains a non string value of type[%s]",
attributeName, value.getClass()));
stringValues.add(value.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
* 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,
this.searchControls, base, filter, params));
}
@ -298,22 +270,15 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
final DistinguishedName searchBaseDn = new DistinguishedName(base);
final NamingEnumeration<SearchResult> resultsEnum = ctx.search(searchBaseDn, filter, params,
buildControls(searchControls));
if (logger.isDebugEnabled()) {
logger.debug("Searching for entry under DN '" + ctxBaseDn + "', base = '" + searchBaseDn + "', filter = '"
+ filter + "'");
}
logger.debug(LogMessage.format("Searching for entry under DN '%s', base = '%s', filter = '%s'", ctxBaseDn,
searchBaseDn, filter));
Set<DirContextOperations> results = new HashSet<>();
try {
while (resultsEnum.hasMore()) {
SearchResult searchResult = resultsEnum.next();
DirContextAdapter dca = (DirContextAdapter) searchResult.getObject();
Assert.notNull(dca, "No object returned by search, DirContext is not correctly configured");
if (logger.isDebugEnabled()) {
logger.debug("Found DN: " + dca.getDn());
}
logger.debug(LogMessage.format("Found DN: %s", dca.getDn()));
results.add(dca);
}
}
@ -321,15 +286,9 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
LdapUtils.closeEnumeration(resultsEnum);
logger.info("Ignoring PartialResultException");
}
if (results.size() == 0) {
throw new IncorrectResultSizeDataAccessException(1, 0);
}
if (results.size() > 1) {
if (results.size() != 1) {
throw new IncorrectResultSizeDataAccessException(1, results.size());
}
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.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
@ -64,33 +65,22 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> this.messages.getMessage("LdapAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
final UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;
UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken) authentication;
String username = userToken.getName();
String password = (String) authentication.getCredentials();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Processing authentication request for user: " + username);
}
this.logger.debug(LogMessage.format("Processing authentication request for user: %s", username));
if (!StringUtils.hasLength(username)) {
throw new BadCredentialsException(
this.messages.getMessage("LdapAuthenticationProvider.emptyUsername", "Empty Username"));
}
if (!StringUtils.hasLength(password)) {
throw new BadCredentialsException(
this.messages.getMessage("AbstractLdapAuthenticationProvider.emptyPassword", "Empty Password"));
}
Assert.notNull(password, "Null password was supplied in authentication token");
DirContextOperations userData = doAuthentication(userToken);
UserDetails user = this.userDetailsContextMapper.mapUserFromContext(userData, authentication.getName(),
loadUserAuthorities(userData, authentication.getName(), (String) authentication.getCredentials()));
return createSuccessfulAuthentication(userToken, user);
}
@ -111,11 +101,9 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati
UserDetails user) {
Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials()
: user.getPassword();
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password,
this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}

View File

@ -91,16 +91,13 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
if (this.userDnFormat == null) {
return Collections.emptyList();
}
List<String> userDns = new ArrayList<>(this.userDnFormat.length);
String[] args = new String[] { LdapEncoder.nameEncode(username) };
synchronized (this.userDnFormat) {
for (MessageFormat formatter : this.userDnFormat) {
userDns.add(formatter.format(args));
}
}
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");
// this.userDnPattern = dnPattern;
this.userDnFormat = new MessageFormat[dnPattern.length];
for (int i = 0; i < dnPattern.length; 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.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
@ -51,7 +52,6 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
* provided.
* @param contextSource the BaseLdapPathContextSource instance against which bind
* operations will be performed.
*
*/
public BindAuthenticator(BaseLdapPathContextSource contextSource) {
super(contextSource);
@ -62,37 +62,30 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
DirContextOperations user = null;
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
String username = authentication.getName();
String password = (String) authentication.getCredentials();
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(
this.messages.getMessage("BindAuthenticator.emptyPassword", "Empty Password"));
}
// If DN patterns are configured, try authenticating with them directly
for (String dn : getUserDns(username)) {
user = bindWithDn(dn, username, password);
if (user != null) {
break;
}
}
// Otherwise use the configured search object to find the user and authenticate
// with the returned DN.
if (user == null && getUserSearch() != null) {
DirContextOperations userFromSearch = getUserSearch().searchForUser(username);
user = bindWithDn(userFromSearch.getDn().toString(), username, password, userFromSearch.getAttributes());
}
if (user == null) {
throw new BadCredentialsException(
this.messages.getMessage("BindAuthenticator.badCredentials", "Bad credentials"));
}
return user;
}
@ -105,26 +98,20 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
DistinguishedName userDn = new DistinguishedName(userDnStr);
DistinguishedName fullDn = new DistinguishedName(userDn);
fullDn.prepend(ctxSource.getBaseLdapPath());
logger.debug("Attempting to bind as " + fullDn);
logger.debug(LogMessage.format("Attempting to bind as %s", fullDn));
DirContext ctx = null;
try {
ctx = getContextSource().getContext(fullDn.toString(), password);
// Check for password policy control
PasswordPolicyControl ppolicy = PasswordPolicyControlExtractor.extractControl(ctx);
logger.debug("Retrieving attributes...");
if (attrs == null || attrs.size() == 0) {
attrs = ctx.getAttributes(userDn, getUserAttributes());
}
DirContextAdapter result = new DirContextAdapter(attrs, userDn, ctxSource.getBaseLdapPath());
if (ppolicy != null) {
result.setAttributeValue(ppolicy.getID(), ppolicy);
}
return result;
}
catch (NamingException ex) {
@ -145,7 +132,6 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
finally {
LdapUtils.closeContext(ctx);
}
return null;
}
@ -155,9 +141,7 @@ public class BindAuthenticator extends AbstractLdapAuthenticator {
* logger.
*/
protected void handleBindException(String userDn, String username, Throwable cause) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to bind as " + userDn + ": " + cause);
}
logger.debug(LogMessage.format("Failed to bind as %s: %s", userDn, cause));
}
}

View File

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

View File

@ -34,18 +34,11 @@ final class LdapEncoder {
private static final int HEX = 16;
private static String[] NAME_ESCAPE_TABLE = new String[96];
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
static {
// Name encoding table -------------------------------------
// all below 0x20 (control chars)
for (char c = 0; c < ' '; c++) {
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
}
NAME_ESCAPE_TABLE['#'] = "\\#";
NAME_ESCAPE_TABLE[','] = "\\,";
NAME_ESCAPE_TABLE[';'] = "\\;";
@ -55,21 +48,21 @@ final class LdapEncoder {
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
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
}
// escapes (RFC2254)
FILTER_ESCAPE_TABLE['*'] = "\\2a";
FILTER_ESCAPE_TABLE['('] = "\\28";
FILTER_ESCAPE_TABLE[')'] = "\\29";
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
FILTER_ESCAPE_TABLE[0] = "\\00";
}
/**
@ -79,15 +72,8 @@ final class LdapEncoder {
}
protected static String toTwoCharHex(char c) {
String raw = Integer.toHexString(c).toUpperCase();
if (raw.length() > 1) {
return raw;
}
else {
return "0" + raw;
}
return (raw.length() > 1) ? raw : "0" + raw;
}
/**
@ -96,29 +82,15 @@ final class LdapEncoder {
* @return a properly escaped representation of the supplied value.
*/
static String filterEncode(String value) {
if (value == null) {
return null;
}
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
if (c < FILTER_ESCAPE_TABLE.length) {
encodedValue.append(FILTER_ESCAPE_TABLE[c]);
}
else {
// default: add the char
encodedValue.append(c);
}
char ch = value.charAt(i);
encodedValue.append((ch < FILTER_ESCAPE_TABLE.length) ? FILTER_ESCAPE_TABLE[ch] : ch);
}
return encodedValue.toString();
}
@ -141,43 +113,31 @@ final class LdapEncoder {
* @return The escaped value.
*/
static String nameEncode(String value) {
if (value == null) {
return null;
}
// make buffer roomy
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
int length = value.length();
int last = length - 1;
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
// space first or last
if (c == ' ' && (i == 0 || i == last)) {
encodedValue.append("\\ ");
continue;
}
// check in table for escapes
if (c < NAME_ESCAPE_TABLE.length) {
// check in table for escapes
String esc = NAME_ESCAPE_TABLE[c];
if (esc != null) {
encodedValue.append(esc);
continue;
}
}
// default: add the char
encodedValue.append(c);
}
return encodedValue.toString();
}
/**
@ -188,43 +148,32 @@ final class LdapEncoder {
* @throws BadLdapGrammarException
*/
static String nameDecode(String value) throws BadLdapGrammarException {
if (value == null) {
return null;
}
// make buffer same size
StringBuilder decoded = new StringBuilder(value.length());
int i = 0;
while (i < value.length()) {
char currentChar = value.charAt(i);
if (currentChar == '\\') {
// Ending with a single backslash is not allowed
if (value.length() <= i + 1) {
// Ending with a single backslash is not allowed
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
}
char nextChar = value.charAt(i + 1);
if (isNormalBackslashEscape(nextChar)) {
decoded.append(nextChar);
i += 2;
}
else {
char nextChar = value.charAt(i + 1);
if (nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || 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;
}
if (value.length() <= i + 2) {
throw new BadLdapGrammarException(
"Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
}
// This should be a hex value
String hexString = "" + nextChar + value.charAt(i + 2);
decoded.append((char) Integer.parseInt(hexString, HEX));
i += 3;
}
}
else {
@ -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.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.NameNotFoundException;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
@ -66,13 +67,10 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
"Can only process UsernamePasswordAuthenticationToken objects");
// locate the user and check the password
DirContextOperations user = null;
String username = authentication.getName();
String password = (String) authentication.getCredentials();
SpringSecurityLdapTemplate ldapTemplate = new SpringSecurityLdapTemplate(getContextSource());
for (String userDn : getUserDns(username)) {
try {
user = ldapTemplate.retrieveEntry(userDn, getUserAttributes());
@ -83,24 +81,20 @@ public final class PasswordComparisonAuthenticator extends AbstractLdapAuthentic
break;
}
}
if (user == null && getUserSearch() != null) {
user = getUserSearch().searchForUser(username);
}
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
}
if (logger.isDebugEnabled()) {
logger.debug("Performing LDAP compare of password attribute '" + this.passwordAttributeName + "' for user '"
+ user.getDn() + "'");
logger.debug(LogMessage.format("Performing LDAP compare of password attribute '%s' for user '%s'",
this.passwordAttributeName, user.getDn()));
}
if (this.usePasswordAttrCompare && isPasswordAttrCompare(user, password)) {
return user;
}
else if (isLdapPasswordCompare(user, ldapTemplate, password)) {
if (isLdapPasswordCompare(user, ldapTemplate, password)) {
return user;
}
throw new BadCredentialsException(

View File

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

View File

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

View File

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

View File

@ -32,7 +32,9 @@ import javax.naming.ldap.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";
private final boolean critical;

View File

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

View File

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

View File

@ -41,20 +41,24 @@ package org.springframework.security.ldap.ppolicy;
*/
public enum PasswordPolicyErrorStatus {
PASSWORD_EXPIRED("ppolicy.expired", "Your password has expired"), ACCOUNT_LOCKED("ppolicy.locked",
"Account is locked"), CHANGE_AFTER_RESET("ppolicy.change.after.reset",
"Your password must be changed after being reset"), PASSWORD_MOD_NOT_ALLOWED(
"ppolicy.mod.not.allowed",
"Password cannot be changed"), MUST_SUPPLY_OLD_PASSWORD("ppolicy.must.supply.old.password",
"The old password must be supplied"), INSUFFICIENT_PASSWORD_QUALITY(
"ppolicy.insufficient.password.quality",
"The supplied password is of insufficient quality"), 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");
PASSWORD_EXPIRED("ppolicy.expired", "Your password has expired"),
ACCOUNT_LOCKED("ppolicy.locked", "Account is locked"),
CHANGE_AFTER_RESET("ppolicy.change.after.reset", "Your password must be changed after being reset"),
PASSWORD_MOD_NOT_ALLOWED("ppolicy.mod.not.allowed", "Password cannot be changed"),
MUST_SUPPLY_OLD_PASSWORD("ppolicy.must.supply.old.password", "The old password must be supplied"),
INSUFFICIENT_PASSWORD_QUALITY("ppolicy.insufficient.password.quality",
"The supplied password is of insufficient quality"),
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;

View File

@ -77,10 +77,7 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
*/
public PasswordPolicyResponseControl(byte[] encodedValue) {
this.encodedValue = encodedValue;
// PPolicyDecoder decoder = new JLdapDecoder();
PPolicyDecoder decoder = new NetscapeDecoder();
try {
decoder.decode();
}
@ -162,23 +159,18 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
@Override
public String toString() {
StringBuilder sb = new StringBuilder("PasswordPolicyResponseControl");
if (hasError()) {
sb.append(", error: ").append(this.errorStatus.getDefaultMessage());
}
if (this.graceLoginsRemaining != Integer.MAX_VALUE) {
sb.append(", warning: ").append(this.graceLoginsRemaining).append(" grace logins remain");
}
if (this.timeBeforeExpiration != Integer.MAX_VALUE) {
sb.append(", warning: time before expiration is ").append(this.timeBeforeExpiration);
}
if (!hasError() && !hasWarning()) {
sb.append(" (no error, no warning)");
}
return sb.toString();
}
@ -198,24 +190,17 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
int[] bread = { 0 };
BERSequence seq = (BERSequence) BERElement.getElement(new SpecificTagDecoder(),
new ByteArrayInputStream(PasswordPolicyResponseControl.this.encodedValue), bread);
int size = seq.size();
if (logger.isDebugEnabled()) {
logger.debug("PasswordPolicyResponse, ASN.1 sequence has " + size + " elements");
}
for (int i = 0; i < seq.size(); i++) {
BERTag elt = (BERTag) seq.elementAt(i);
int tag = elt.getTag() & 0x1F;
if (tag == 0) {
BERChoice warning = (BERChoice) elt.getValue();
BERTag content = (BERTag) warning.getValue();
int value = ((BERInteger) content.getValue()).getValue();
if ((content.getTag() & 0x1F) == 0) {
PasswordPolicyResponseControl.this.timeBeforeExpiration = value;
}
@ -241,19 +226,15 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
boolean[] implicit) throws IOException {
tag &= 0x1F;
implicit[0] = false;
if (tag == 0) {
// Either the choice or the time before expiry within it
if (this.inChoice == null) {
setInChoice(true);
// Read the choice length from the stream (ignored)
BERElement.readLengthOctets(stream, bytesRead);
int[] componentLength = new int[1];
BERElement choice = new BERChoice(decoder, stream, componentLength);
bytesRead[0] += componentLength[0];
// inChoice = null;
return choice;
}
@ -267,7 +248,6 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
if (this.inChoice == null) {
// The enumeration
setInChoice(false);
return new BEREnumerated(stream, bytesRead);
}
else {
@ -277,7 +257,6 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
}
}
}
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.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.ContextSource;
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(searchFilter, "searchFilter must not be null.");
Assert.notNull(searchBase, "searchBase must not be null (an empty string is acceptable).");
this.searchFilter = searchFilter;
this.contextSource = contextSource;
this.searchBase = searchBase;
setSearchSubtree(true);
if (searchBase.length() == 0) {
logger.info(
"SearchBase not set. Searches will be performed from the root: " + contextSource.getBaseLdapPath());
@ -95,26 +93,18 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
*/
@Override
public DirContextOperations searchForUser(String username) {
if (logger.isDebugEnabled()) {
logger.debug("Searching for user '" + username + "', with user search " + this);
}
logger.debug(LogMessage.of(() -> "Searching for user '" + username + "', with user search " + this));
SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(this.contextSource);
template.setSearchControls(this.searchControls);
try {
return template.searchForSingleEntry(this.searchBase, this.searchFilter, new String[] { username });
}
catch (IncorrectResultSizeDataAccessException notFound) {
if (notFound.getActualSize() == 0) {
catch (IncorrectResultSizeDataAccessException ex) {
if (ex.getActualSize() == 0) {
throw new UsernameNotFoundException("User " + username + " not found in directory.");
}
// Search should never return multiple results if properly configured, so just
// rethrow
throw notFound;
// Search should never return multiple results if properly configured
throw ex;
}
}
@ -161,7 +151,6 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[ searchFilter: '").append(this.searchFilter).append("', ");
sb.append("searchBase: '").append(this.searchBase).append("'");
sb.append(", scope: ").append(

View File

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

View File

@ -85,20 +85,16 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
if (isRunning()) {
return;
}
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(this.defaultPartitionSuffix);
config.addAdditionalBindCredentials("uid=admin,ou=system", "secret");
config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("LDAP", this.port));
config.setEnforceSingleStructuralObjectClass(false);
config.setEnforceAttributeSyntaxCompliance(true);
DN dn = new DN(this.defaultPartitionSuffix);
Entry entry = new Entry(dn);
entry.addAttribute("objectClass", "top", "domain", "extensibleObject");
entry.addAttribute("dc", dn.getRDN().getAttributeValues()[0]);
InMemoryDirectoryServer directoryServer = new InMemoryDirectoryServer(config);
directoryServer.add(entry);
importLdif(directoryServer);
@ -110,7 +106,6 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif
catch (LDAPException ex) {
throw new RuntimeException("Server startup failed", ex);
}
}
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.LogFactory;
import org.springframework.core.log.LogMessage;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
@ -161,21 +162,17 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
this.ldapTemplate = new SpringSecurityLdapTemplate(contextSource);
getLdapTemplate().setSearchControls(getSearchControls());
this.groupSearchBase = groupSearchBase;
if (groupSearchBase == null) {
logger.info("groupSearchBase is null. No group search will be performed.");
}
else if (groupSearchBase.length() == 0) {
logger.info("groupSearchBase is empty. Searches will be performed from the context source base");
}
this.authorityMapper = (record) -> {
String role = record.get(this.groupRoleAttribute).get(0);
if (this.convertToUpperCase) {
role = role.toUpperCase();
}
return new SimpleGrantedAuthority(this.rolePrefix + role);
};
}
@ -202,26 +199,17 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
@Override
public final Collection<GrantedAuthority> getGrantedAuthorities(DirContextOperations user, String username) {
String userDn = user.getNameInNamespace();
if (logger.isDebugEnabled()) {
logger.debug("Getting authorities for user " + userDn);
}
logger.debug(LogMessage.format("Getting authorities for user %s", userDn));
Set<GrantedAuthority> roles = getGroupMembershipRoles(userDn, username);
Set<GrantedAuthority> extraRoles = getAdditionalRoles(user, username);
if (extraRoles != null) {
roles.addAll(extraRoles);
}
if (this.defaultRole != null) {
roles.add(this.defaultRole);
}
List<GrantedAuthority> result = new ArrayList<>(roles.size());
result.addAll(roles);
return result;
}
@ -229,26 +217,16 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
if (getGroupSearchBase() == null) {
return new HashSet<>();
}
Set<GrantedAuthority> authorities = new HashSet<>();
if (logger.isDebugEnabled()) {
logger.debug("Searching for roles for user '" + username + "', DN = " + "'" + userDn + "', with filter "
+ this.groupSearchFilter + " in search base '" + getGroupSearchBase() + "'");
}
logger.debug(LogMessage.of(() -> "Searching for roles for user '" + username + "', DN = " + "'" + userDn
+ "', with filter " + this.groupSearchFilter + " in search base '" + getGroupSearchBase() + "'"));
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
getGroupSearchBase(), this.groupSearchFilter, new String[] { userDn, username },
new String[] { this.groupRoleAttribute });
if (logger.isDebugEnabled()) {
logger.debug("Roles from search: " + userRoles);
}
logger.debug(LogMessage.of(() -> "Roles from search: " + userRoles));
for (Map<String, List<String>> role : userRoles) {
authorities.add(this.authorityMapper.apply(role));
}
return authorities;
}

View File

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

View File

@ -33,10 +33,8 @@ public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) {
InetOrgPerson.Essence p = new InetOrgPerson.Essence(ctx);
p.setUsername(username);
p.setAuthorities(authorities);
return p.createUserDetails();
}
@ -44,7 +42,6 @@ public class InetOrgPersonContextMapper implements UserDetailsContextMapper {
@Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
Assert.isInstanceOf(InetOrgPerson.class, user, "UserDetails must be an InetOrgPerson instance");
InetOrgPerson p = (InetOrgPerson) user;
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) {
Assert.notNull(role, "role can not be null");
Assert.notNull(dn, "dn can not be null");
this.role = role;
this.dn = dn;
this.attributes = attributes;
@ -87,10 +86,7 @@ public class LdapAuthority implements GrantedAuthority {
if (this.attributes != null) {
result = this.attributes.get(name);
}
if (result == null) {
result = Collections.emptyList();
}
return result;
return (result != null) ? result : Collections.emptyList();
}
/**
@ -100,17 +96,9 @@ public class LdapAuthority implements GrantedAuthority {
*/
public String getFirstAttributeValue(String name) {
List<String> result = getAttributeValues(name);
if (result.isEmpty()) {
return null;
}
else {
return result.get(0);
}
return (!result.isEmpty()) ? result.get(0) : null;
}
/**
* {@inheritDoc}
*/
@Override
public String getAuthority() {
return this.role;
@ -118,23 +106,21 @@ public class LdapAuthority implements GrantedAuthority {
/**
* Compares the LdapAuthority based on {@link #getAuthority()} and {@link #getDn()}
* values {@inheritDoc}
* values.
*/
@Override
public boolean equals(Object o) {
if (this == o) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(o instanceof LdapAuthority)) {
if (!(obj instanceof LdapAuthority)) {
return false;
}
LdapAuthority that = (LdapAuthority) o;
if (!this.dn.equals(that.dn)) {
LdapAuthority other = (LdapAuthority) obj;
if (!this.dn.equals(other.dn)) {
return false;
}
return this.role.equals(that.role);
return this.role.equals(other.role);
}
@Override

View File

@ -154,11 +154,9 @@ public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData
sb.append("AccountNonExpired: ").append(this.accountNonExpired).append("; ");
sb.append("CredentialsNonExpired: ").append(this.credentialsNonExpired).append("; ");
sb.append("AccountNonLocked: ").append(this.accountNonLocked).append("; ");
if (this.getAuthorities() != null && !this.getAuthorities().isEmpty()) {
sb.append("Granted Authorities: ");
boolean first = true;
for (Object authority : this.getAuthorities()) {
if (first) {
first = false;
@ -166,14 +164,12 @@ public class LdapUserDetailsImpl implements LdapUserDetails, PasswordPolicyData
else {
sb.append(", ");
}
sb.append(authority.toString());
}
}
else {
sb.append("Not granted any authorities");
}
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.username, "username must not be null");
Assert.notNull(this.instance.getDn(), "Distinguished name must not be null");
this.instance.authorities = Collections.unmodifiableList(this.mutableAuthorities);
LdapUserDetails newInstance = this.instance;
this.instance = null;
return newInstance;
}

View File

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

View File

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

View File

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

View File

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

View File

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