parent
55895f3b08
commit
78d2be9bd5
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.security.ldap;
|
package org.springframework.security.ldap;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -84,7 +83,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
||||||
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
|
setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("unchecked")
|
||||||
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 authenticating as the 'manager' user.
|
// Remove the pooling flag unless authenticating as the 'manager' user.
|
||||||
|
@ -145,7 +144,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
||||||
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 (trimmedUrl.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
providerUrl.append(trimmedUrl);
|
providerUrl.append(trimmedUrl);
|
||||||
|
@ -160,21 +159,11 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String encodeUrl(String url) {
|
private static String encodeUrl(String url) {
|
||||||
try {
|
return URLEncoder.encode(url, StandardCharsets.UTF_8);
|
||||||
return URLEncoder.encode(url, StandardCharsets.UTF_8.toString());
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String decodeUrl(String url) {
|
private String decodeUrl(String url) {
|
||||||
try {
|
return URLDecoder.decode(url, StandardCharsets.UTF_8);
|
||||||
return URLDecoder.decode(url, StandardCharsets.UTF_8.toString());
|
|
||||||
}
|
|
||||||
catch (UnsupportedEncodingException ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2005-2010 the original author or authors.
|
* Copyright 2005-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.security.ldap;
|
package org.springframework.security.ldap;
|
||||||
|
|
||||||
import org.springframework.ldap.BadLdapGrammarException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to encode and decode ldap names and values.
|
* Helper class to encode and decode ldap names and values.
|
||||||
*
|
*
|
||||||
|
@ -31,26 +29,7 @@ import org.springframework.ldap.BadLdapGrammarException;
|
||||||
*/
|
*/
|
||||||
final class LdapEncoder {
|
final class LdapEncoder {
|
||||||
|
|
||||||
private static final int HEX = 16;
|
private static final String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
|
||||||
|
|
||||||
private static String[] NAME_ESCAPE_TABLE = new String[96];
|
|
||||||
static {
|
|
||||||
// all below 0x20 (control chars)
|
|
||||||
for (char c = 0; c < ' '; c++) {
|
|
||||||
NAME_ESCAPE_TABLE[c] = "\\" + toTwoCharHex(c);
|
|
||||||
}
|
|
||||||
NAME_ESCAPE_TABLE['#'] = "\\#";
|
|
||||||
NAME_ESCAPE_TABLE[','] = "\\,";
|
|
||||||
NAME_ESCAPE_TABLE[';'] = "\\;";
|
|
||||||
NAME_ESCAPE_TABLE['='] = "\\=";
|
|
||||||
NAME_ESCAPE_TABLE['+'] = "\\+";
|
|
||||||
NAME_ESCAPE_TABLE['<'] = "\\<";
|
|
||||||
NAME_ESCAPE_TABLE['>'] = "\\>";
|
|
||||||
NAME_ESCAPE_TABLE['\"'] = "\\\"";
|
|
||||||
NAME_ESCAPE_TABLE['\\'] = "\\\\";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// fill with char itself
|
// fill with char itself
|
||||||
|
@ -71,11 +50,6 @@ final class LdapEncoder {
|
||||||
private LdapEncoder() {
|
private LdapEncoder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String toTwoCharHex(char c) {
|
|
||||||
String raw = Integer.toHexString(c).toUpperCase();
|
|
||||||
return (raw.length() > 1) ? raw : "0" + raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape a value for use in a filter.
|
* Escape a value for use in a filter.
|
||||||
* @param value the value to escape.
|
* @param value the value to escape.
|
||||||
|
@ -94,102 +68,4 @@ final class LdapEncoder {
|
||||||
return encodedValue.toString();
|
return encodedValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
|
|
||||||
*
|
|
||||||
* <br/>
|
|
||||||
* Escapes:<br/>
|
|
||||||
* ' ' [space] - "\ " [if first or last] <br/>
|
|
||||||
* '#' [hash] - "\#" <br/>
|
|
||||||
* ',' [comma] - "\," <br/>
|
|
||||||
* ';' [semicolon] - "\;" <br/>
|
|
||||||
* '= [equals] - "\=" <br/>
|
|
||||||
* '+' [plus] - "\+" <br/>
|
|
||||||
* '<' [less than] - "\<" <br/>
|
|
||||||
* '>' [greater than] - "\>" <br/>
|
|
||||||
* '"' [double quote] - "\"" <br/>
|
|
||||||
* '\' [backslash] - "\\" <br/>
|
|
||||||
* @param value the value to escape.
|
|
||||||
* @return The escaped value.
|
|
||||||
*/
|
|
||||||
static String nameEncode(String value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
|
||||||
int length = value.length();
|
|
||||||
int last = length - 1;
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
char c = value.charAt(i);
|
|
||||||
// space first or last
|
|
||||||
if (c == ' ' && (i == 0 || i == last)) {
|
|
||||||
encodedValue.append("\\ ");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// check in table for escapes
|
|
||||||
if (c < NAME_ESCAPE_TABLE.length) {
|
|
||||||
String esc = NAME_ESCAPE_TABLE[c];
|
|
||||||
if (esc != null) {
|
|
||||||
encodedValue.append(esc);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// default: add the char
|
|
||||||
encodedValue.append(c);
|
|
||||||
}
|
|
||||||
return encodedValue.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a value. Converts escaped chars to ordinary chars.
|
|
||||||
* @param value Trimmed value, so no leading an trailing blanks, except an escaped
|
|
||||||
* space last.
|
|
||||||
* @return The decoded value as a string.
|
|
||||||
* @throws BadLdapGrammarException
|
|
||||||
*/
|
|
||||||
static String nameDecode(String value) throws BadLdapGrammarException {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuilder decoded = new StringBuilder(value.length());
|
|
||||||
int i = 0;
|
|
||||||
while (i < value.length()) {
|
|
||||||
char currentChar = value.charAt(i);
|
|
||||||
if (currentChar == '\\') {
|
|
||||||
// Ending with a single backslash is not allowed
|
|
||||||
if (value.length() <= i + 1) {
|
|
||||||
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
|
|
||||||
}
|
|
||||||
char nextChar = value.charAt(i + 1);
|
|
||||||
if (isNormalBackslashEscape(nextChar)) {
|
|
||||||
decoded.append(nextChar);
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (value.length() <= i + 2) {
|
|
||||||
throw new BadLdapGrammarException(
|
|
||||||
"Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
|
|
||||||
}
|
|
||||||
// This should be a hex value
|
|
||||||
String hexString = "" + nextChar + value.charAt(i + 2);
|
|
||||||
decoded.append((char) Integer.parseInt(hexString, HEX));
|
|
||||||
i += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// This character wasn't escaped - just append it
|
|
||||||
decoded.append(currentChar);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return decoded.toString();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isNormalBackslashEscape(char nextChar) {
|
|
||||||
return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|
|
||||||
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,7 +83,7 @@ public final class LdapUtils {
|
||||||
*/
|
*/
|
||||||
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.isEmpty()) {
|
||||||
return fullDn;
|
return fullDn;
|
||||||
}
|
}
|
||||||
LdapName base = LdapNameBuilder.newInstance(baseDn).build();
|
LdapName base = LdapNameBuilder.newInstance(baseDn).build();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2013 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -18,7 +18,7 @@ package org.springframework.security.ldap;
|
||||||
|
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -40,7 +40,6 @@ import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.core.log.LogMessage;
|
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.ContextMapper;
|
import org.springframework.ldap.core.ContextMapper;
|
||||||
import org.springframework.ldap.core.ContextSource;
|
import org.springframework.ldap.core.ContextSource;
|
||||||
import org.springframework.ldap.core.DirContextAdapter;
|
import org.springframework.ldap.core.DirContextAdapter;
|
||||||
|
@ -98,7 +97,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
||||||
searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
searchControls.setSearchScope(SearchControls.OBJECT_SCOPE);
|
||||||
Object[] params = new Object[] { value };
|
Object[] params = new Object[] { value };
|
||||||
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, params, searchControls);
|
NamingEnumeration<SearchResult> results = ctx.search(dn, comparisonFilter, params, searchControls);
|
||||||
Boolean match = results.hasMore();
|
boolean match = results.hasMore();
|
||||||
LdapUtils.closeEnumeration(results);
|
LdapUtils.closeEnumeration(results);
|
||||||
return match;
|
return match;
|
||||||
});
|
});
|
||||||
|
@ -112,7 +111,7 @@ 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 executeReadOnly((ctx) -> {
|
||||||
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
|
Attributes attrs = ctx.getAttributes(dn, attributesToRetrieve);
|
||||||
return new DirContextAdapter(attrs, LdapNameBuilder.newInstance(dn).build(),
|
return new DirContextAdapter(attrs, LdapNameBuilder.newInstance(dn).build(),
|
||||||
LdapNameBuilder.newInstance(ctx.getNameInNamespace()).build());
|
LdapNameBuilder.newInstance(ctx.getNameInNamespace()).build());
|
||||||
|
@ -169,18 +168,19 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
||||||
String formattedFilter = MessageFormat.format(filter, encodedParams);
|
String formattedFilter = MessageFormat.format(filter, encodedParams);
|
||||||
logger.trace(LogMessage.format("Using filter: %s", formattedFilter));
|
logger.trace(LogMessage.format("Using filter: %s", formattedFilter));
|
||||||
HashSet<Map<String, List<String>>> result = new HashSet<>();
|
HashSet<Map<String, List<String>>> result = 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 (ObjectUtils.isEmpty(attributeNames)) {
|
if (ObjectUtils.isEmpty(attributeNames)) {
|
||||||
try {
|
try {
|
||||||
for (NamingEnumeration enumeration = adapter.getAttributes().getAll(); enumeration.hasMore();) {
|
for (NamingEnumeration<? extends Attribute> enumeration = adapter.getAttributes()
|
||||||
Attribute attr = (Attribute) enumeration.next();
|
.getAll(); enumeration.hasMore();) {
|
||||||
|
Attribute attr = enumeration.next();
|
||||||
extractStringAttributeValues(adapter, record, attr.getID());
|
extractStringAttributeValues(adapter, record, attr.getID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (NamingException ex) {
|
catch (NamingException ex) {
|
||||||
org.springframework.ldap.support.LdapUtils.convertLdapException(ex);
|
throw org.springframework.ldap.support.LdapUtils.convertLdapException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -188,7 +188,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
||||||
extractStringAttributeValues(adapter, record, attributeName);
|
extractStringAttributeValues(adapter, record, attributeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
record.put(DN_KEY, Arrays.asList(getAdapterDN(adapter)));
|
record.put(DN_KEY, Collections.singletonList(getAdapterDN(adapter)));
|
||||||
result.add(record);
|
result.add(record);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -258,8 +258,7 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
||||||
* search returns more than one result.
|
* search returns more than one result.
|
||||||
*/
|
*/
|
||||||
public DirContextOperations searchForSingleEntry(String base, String filter, Object[] params) {
|
public DirContextOperations searchForSingleEntry(String base, String filter, Object[] params) {
|
||||||
return (DirContextOperations) executeReadOnly((ContextExecutor) (ctx) -> searchForSingleEntryInternal(ctx,
|
return executeReadOnly((ctx) -> searchForSingleEntryInternal(ctx, this.searchControls, base, filter, params));
|
||||||
this.searchControls, base, filter, params));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -296,8 +295,9 @@ public class SpringSecurityLdapTemplate extends LdapTemplate {
|
||||||
/**
|
/**
|
||||||
* We need to make sure the search controls has the return object flag set to true, in
|
* We need to make sure the search controls has the return object flag set to true, in
|
||||||
* order for the search to return DirContextAdapter instances.
|
* order for the search to return DirContextAdapter instances.
|
||||||
* @param originalControls
|
* @param originalControls the {@link SearchControls} that might have the return
|
||||||
* @return
|
* object flag set to true
|
||||||
|
* @return a {@link SearchControls} that does have the return object flag set to true
|
||||||
*/
|
*/
|
||||||
private static SearchControls buildControls(SearchControls originalControls) {
|
private static SearchControls buildControls(SearchControls originalControls) {
|
||||||
return new SearchControls(originalControls.getSearchScope(), originalControls.getCountLimit(),
|
return new SearchControls(originalControls.getSearchScope(), originalControls.getCountLimit(),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.context.MessageSource;
|
import org.springframework.context.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.lang.NonNull;
|
||||||
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;
|
||||||
|
@ -117,14 +118,15 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati
|
||||||
* obtained from the UserDetails object created by the configured
|
* obtained from the UserDetails object created by the configured
|
||||||
* {@code UserDetailsContextMapper}. Often it will not be possible to read the
|
* {@code UserDetailsContextMapper}. Often it will not be possible to read the
|
||||||
* password from the directory, so defaults to true.
|
* password from the directory, so defaults to true.
|
||||||
* @param useAuthenticationRequestCredentials
|
* @param useAuthenticationRequestCredentials whether to use the credentials in the
|
||||||
|
* authentication request
|
||||||
*/
|
*/
|
||||||
public void setUseAuthenticationRequestCredentials(boolean useAuthenticationRequestCredentials) {
|
public void setUseAuthenticationRequestCredentials(boolean useAuthenticationRequestCredentials) {
|
||||||
this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials;
|
this.useAuthenticationRequestCredentials = useAuthenticationRequestCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMessageSource(MessageSource messageSource) {
|
public void setMessageSource(@NonNull MessageSource messageSource) {
|
||||||
this.messages = new MessageSourceAccessor(messageSource);
|
this.messages = new MessageSourceAccessor(messageSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.springframework.beans.factory.InitializingBean;
|
||||||
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.lang.NonNull;
|
||||||
import org.springframework.ldap.core.ContextSource;
|
import org.springframework.ldap.core.ContextSource;
|
||||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||||
import org.springframework.security.ldap.search.LdapUserSearch;
|
import org.springframework.security.ldap.search.LdapUserSearch;
|
||||||
|
@ -37,6 +38,8 @@ import org.springframework.util.Assert;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, InitializingBean, MessageSourceAware {
|
public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, InitializingBean, MessageSourceAware {
|
||||||
|
|
||||||
|
private final Object mutex = new Object();
|
||||||
|
|
||||||
private final ContextSource contextSource;
|
private final ContextSource contextSource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,7 +62,7 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an initialized instance with the {@link ContextSource} provided.
|
* Create an initialized instance with the {@link ContextSource} provided.
|
||||||
* @param contextSource
|
* @param contextSource the {@link ContextSource} to use
|
||||||
*/
|
*/
|
||||||
public AbstractLdapAuthenticator(ContextSource contextSource) {
|
public AbstractLdapAuthenticator(ContextSource contextSource) {
|
||||||
Assert.notNull(contextSource, "contextSource must not be null.");
|
Assert.notNull(contextSource, "contextSource must not be null.");
|
||||||
|
@ -93,7 +96,7 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
|
||||||
}
|
}
|
||||||
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.mutex) {
|
||||||
for (MessageFormat formatter : this.userDnFormat) {
|
for (MessageFormat formatter : this.userDnFormat) {
|
||||||
userDns.add(formatter.format(args));
|
userDns.add(formatter.format(args));
|
||||||
}
|
}
|
||||||
|
@ -106,14 +109,14 @@ public abstract class AbstractLdapAuthenticator implements LdapAuthenticator, In
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMessageSource(MessageSource messageSource) {
|
public void setMessageSource(@NonNull MessageSource messageSource) {
|
||||||
Assert.notNull(messageSource, "Message source must not be null");
|
Assert.notNull(messageSource, "Message source must not be null");
|
||||||
this.messages = new MessageSourceAccessor(messageSource);
|
this.messages = new MessageSourceAccessor(messageSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the user attributes which will be retrieved from the directory.
|
* Sets the user attributes which will be retrieved from the directory.
|
||||||
* @param userAttributes
|
* @param userAttributes the set of user attributes to retrieve
|
||||||
*/
|
*/
|
||||||
public void setUserAttributes(String[] userAttributes) {
|
public void setUserAttributes(String[] userAttributes) {
|
||||||
Assert.notNull(userAttributes, "The userAttributes property cannot be set to null");
|
Assert.notNull(userAttributes, "The userAttributes property cannot be set to null");
|
||||||
|
|
|
@ -33,7 +33,7 @@ public interface LdapAuthenticator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticates as a user and obtains additional user information from the directory.
|
* Authenticates as a user and obtains additional user information from the directory.
|
||||||
* @param authentication
|
* @param authentication the authentication request
|
||||||
* @return the details of the successfully authenticated user.
|
* @return the details of the successfully authenticated user.
|
||||||
*/
|
*/
|
||||||
DirContextOperations authenticate(Authentication authentication);
|
DirContextOperations authenticate(Authentication authentication);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2005-2010 the original author or authors.
|
* Copyright 2005-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,8 +16,6 @@
|
||||||
|
|
||||||
package org.springframework.security.ldap.authentication;
|
package org.springframework.security.ldap.authentication;
|
||||||
|
|
||||||
import org.springframework.ldap.BadLdapGrammarException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to encode and decode ldap names and values.
|
* Helper class to encode and decode ldap names and values.
|
||||||
*
|
*
|
||||||
|
@ -31,9 +29,7 @@ import org.springframework.ldap.BadLdapGrammarException;
|
||||||
*/
|
*/
|
||||||
final class LdapEncoder {
|
final class LdapEncoder {
|
||||||
|
|
||||||
private static final int HEX = 16;
|
private static final String[] NAME_ESCAPE_TABLE = new String[96];
|
||||||
|
|
||||||
private static String[] NAME_ESCAPE_TABLE = new String[96];
|
|
||||||
static {
|
static {
|
||||||
// all below 0x20 (control chars)
|
// all below 0x20 (control chars)
|
||||||
for (char c = 0; c < ' '; c++) {
|
for (char c = 0; c < ' '; c++) {
|
||||||
|
@ -50,54 +46,19 @@ final class LdapEncoder {
|
||||||
NAME_ESCAPE_TABLE['\\'] = "\\\\";
|
NAME_ESCAPE_TABLE['\\'] = "\\\\";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] FILTER_ESCAPE_TABLE = new String['\\' + 1];
|
|
||||||
|
|
||||||
static {
|
|
||||||
// fill with char itself
|
|
||||||
for (char c = 0; c < FILTER_ESCAPE_TABLE.length; c++) {
|
|
||||||
FILTER_ESCAPE_TABLE[c] = String.valueOf(c);
|
|
||||||
}
|
|
||||||
// escapes (RFC2254)
|
|
||||||
FILTER_ESCAPE_TABLE['*'] = "\\2a";
|
|
||||||
FILTER_ESCAPE_TABLE['('] = "\\28";
|
|
||||||
FILTER_ESCAPE_TABLE[')'] = "\\29";
|
|
||||||
FILTER_ESCAPE_TABLE['\\'] = "\\5c";
|
|
||||||
FILTER_ESCAPE_TABLE[0] = "\\00";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All static methods - not to be instantiated.
|
* All static methods - not to be instantiated.
|
||||||
*/
|
*/
|
||||||
private LdapEncoder() {
|
private LdapEncoder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static String toTwoCharHex(char c) {
|
static String toTwoCharHex(char c) {
|
||||||
String raw = Integer.toHexString(c).toUpperCase();
|
String raw = Integer.toHexString(c).toUpperCase();
|
||||||
return (raw.length() > 1) ? raw : "0" + raw;
|
return (raw.length() > 1) ? raw : "0" + raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape a value for use in a filter.
|
* LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI! <br/>
|
||||||
* @param value the value to escape.
|
|
||||||
* @return a properly escaped representation of the supplied value.
|
|
||||||
*/
|
|
||||||
static String filterEncode(String value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuilder encodedValue = new StringBuilder(value.length() * 2);
|
|
||||||
int length = value.length();
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
char ch = value.charAt(i);
|
|
||||||
encodedValue.append((ch < FILTER_ESCAPE_TABLE.length) ? FILTER_ESCAPE_TABLE[ch] : ch);
|
|
||||||
}
|
|
||||||
return encodedValue.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
|
|
||||||
*
|
|
||||||
* <br/>
|
|
||||||
* Escapes:<br/>
|
* Escapes:<br/>
|
||||||
* ' ' [space] - "\ " [if first or last] <br/>
|
* ' ' [space] - "\ " [if first or last] <br/>
|
||||||
* '#' [hash] - "\#" <br/>
|
* '#' [hash] - "\#" <br/>
|
||||||
|
@ -140,56 +101,4 @@ final class LdapEncoder {
|
||||||
return encodedValue.toString();
|
return encodedValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decodes a value. Converts escaped chars to ordinary chars.
|
|
||||||
* @param value Trimmed value, so no leading an trailing blanks, except an escaped
|
|
||||||
* space last.
|
|
||||||
* @return The decoded value as a string.
|
|
||||||
* @throws BadLdapGrammarException
|
|
||||||
*/
|
|
||||||
static String nameDecode(String value) throws BadLdapGrammarException {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
StringBuilder decoded = new StringBuilder(value.length());
|
|
||||||
int i = 0;
|
|
||||||
while (i < value.length()) {
|
|
||||||
char currentChar = value.charAt(i);
|
|
||||||
if (currentChar == '\\') {
|
|
||||||
// Ending with a single backslash is not allowed
|
|
||||||
if (value.length() <= i + 1) {
|
|
||||||
throw new BadLdapGrammarException("Unexpected end of value " + "unterminated '\\'");
|
|
||||||
}
|
|
||||||
char nextChar = value.charAt(i + 1);
|
|
||||||
if (isNormalBackslashEscape(nextChar)) {
|
|
||||||
decoded.append(nextChar);
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (value.length() <= i + 2) {
|
|
||||||
throw new BadLdapGrammarException(
|
|
||||||
"Unexpected end of value " + "expected special or hex, found '" + nextChar + "'");
|
|
||||||
}
|
|
||||||
// This should be a hex value
|
|
||||||
String hexString = "" + nextChar + value.charAt(i + 2);
|
|
||||||
decoded.append((char) Integer.parseInt(hexString, HEX));
|
|
||||||
i += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// This character wasn't escaped - just append it
|
|
||||||
decoded.append(currentChar);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return decoded.toString();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isNormalBackslashEscape(char nextChar) {
|
|
||||||
return nextChar == ',' || nextChar == '=' || nextChar == '+' || nextChar == '<' || nextChar == '>'
|
|
||||||
|| nextChar == '#' || nextChar == ';' || nextChar == '\\' || nextChar == '\"' || nextChar == ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -57,8 +57,7 @@ public class SpringSecurityAuthenticationSource implements AuthenticationSource
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
Object principal = authentication.getPrincipal();
|
Object principal = authentication.getPrincipal();
|
||||||
if (principal instanceof LdapUserDetails) {
|
if (principal instanceof LdapUserDetails details) {
|
||||||
LdapUserDetails details = (LdapUserDetails) principal;
|
|
||||||
return details.getDn();
|
return details.getDn();
|
||||||
}
|
}
|
||||||
if (authentication instanceof AnonymousAuthenticationToken) {
|
if (authentication instanceof AnonymousAuthenticationToken) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -40,7 +40,6 @@ import org.springframework.security.core.AuthenticationException;
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public final class ActiveDirectoryAuthenticationException extends AuthenticationException {
|
public final class ActiveDirectoryAuthenticationException extends AuthenticationException {
|
||||||
|
|
||||||
private final String dataCode;
|
private final String dataCode;
|
||||||
|
|
|
@ -189,12 +189,11 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends AbstractLda
|
||||||
|
|
||||||
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;
|
|
||||||
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);
|
||||||
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
|
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
|
||||||
env.put(Context.PROVIDER_URL, bindUrl);
|
env.put(Context.PROVIDER_URL, this.url);
|
||||||
env.put(Context.SECURITY_CREDENTIALS, password);
|
env.put(Context.SECURITY_CREDENTIALS, password);
|
||||||
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());
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2015-2021 the original author or authors.
|
* Copyright 2015-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -30,7 +30,7 @@ import org.springframework.security.ldap.userdetails.InetOrgPerson;
|
||||||
* @see LdapJackson2Module
|
* @see LdapJackson2Module
|
||||||
* @see SecurityJackson2Modules
|
* @see SecurityJackson2Modules
|
||||||
*/
|
*/
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2015-2021 the original author or authors.
|
* Copyright 2015-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -35,7 +35,7 @@ import org.springframework.security.ldap.userdetails.LdapAuthority;
|
||||||
* @see LdapJackson2Module
|
* @see LdapJackson2Module
|
||||||
* @see SecurityJackson2Modules
|
* @see SecurityJackson2Modules
|
||||||
*/
|
*/
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
|
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
abstract class LdapAuthorityMixin {
|
abstract class LdapAuthorityMixin {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2015-2021 the original author or authors.
|
* Copyright 2015-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -30,6 +30,7 @@ import org.springframework.security.ldap.userdetails.Person;
|
||||||
* {@link LdapAuthorityMixin}, {@link LdapUserDetailsImplMixin}, {@link PersonMixin},
|
* {@link LdapAuthorityMixin}, {@link LdapUserDetailsImplMixin}, {@link PersonMixin},
|
||||||
* {@link InetOrgPersonMixin}.
|
* {@link InetOrgPersonMixin}.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* If not already enabled, default typing will be automatically enabled as type info is
|
* If not already enabled, default typing will be automatically enabled as type info is
|
||||||
* required to properly serialize/deserialize objects. In order to use this module just
|
* required to properly serialize/deserialize objects. In order to use this module just
|
||||||
* add it to your {@code ObjectMapper} configuration.
|
* add it to your {@code ObjectMapper} configuration.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2015-2021 the original author or authors.
|
* Copyright 2015-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -30,7 +30,7 @@ import org.springframework.security.ldap.userdetails.LdapUserDetailsImpl;
|
||||||
* @see LdapJackson2Module
|
* @see LdapJackson2Module
|
||||||
* @see SecurityJackson2Modules
|
* @see SecurityJackson2Modules
|
||||||
*/
|
*/
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2015-2021 the original author or authors.
|
* Copyright 2015-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -30,7 +30,7 @@ import org.springframework.security.ldap.userdetails.Person;
|
||||||
* @see LdapJackson2Module
|
* @see LdapJackson2Module
|
||||||
* @see SecurityJackson2Modules
|
* @see SecurityJackson2Modules
|
||||||
*/
|
*/
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
|
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
|
||||||
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
|
||||||
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -77,7 +77,7 @@ public class PasswordPolicyAwareContextSource extends DefaultSpringSecurityConte
|
||||||
@Override
|
@Override
|
||||||
@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<String, Object> 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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,7 +220,7 @@ public class PasswordPolicyResponseControl extends PasswordPolicyControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpecificTagDecoder extends BERTagDecoder {
|
static class SpecificTagDecoder extends BERTagDecoder {
|
||||||
|
|
||||||
/** Allows us to remember which of the two options we're decoding */
|
/** Allows us to remember which of the two options we're decoding */
|
||||||
private Boolean inChoice = null;
|
private Boolean inChoice = null;
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
|
||||||
/**
|
/**
|
||||||
* Context name to search in, relative to the base of the configured ContextSource.
|
* Context name to search in, relative to the base of the configured ContextSource.
|
||||||
*/
|
*/
|
||||||
private String searchBase = "";
|
private final String searchBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The filter expression used in the user search. This is an LDAP search filter (as
|
* The filter expression used in the user search. This is an LDAP search filter (as
|
||||||
|
@ -78,9 +78,9 @@ public class FilterBasedLdapUserSearch implements LdapUserSearch {
|
||||||
this.contextSource = contextSource;
|
this.contextSource = contextSource;
|
||||||
this.searchBase = searchBase;
|
this.searchBase = searchBase;
|
||||||
setSearchSubtree(true);
|
setSearchSubtree(true);
|
||||||
if (searchBase.length() == 0) {
|
if (searchBase.isEmpty()) {
|
||||||
logger.info(LogMessage.format("Searches will be performed from the root %s since SearchBase not set",
|
logger.info(LogMessage.format("Searches will be performed from the root %s since SearchBase not set",
|
||||||
contextSource.getBaseLdapPath()));
|
contextSource.getBaseLdapName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -33,6 +33,7 @@ import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.context.Lifecycle;
|
import org.springframework.context.Lifecycle;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,7 +44,7 @@ public class UnboundIdContainer
|
||||||
|
|
||||||
private InMemoryDirectoryServer directoryServer;
|
private InMemoryDirectoryServer directoryServer;
|
||||||
|
|
||||||
private String defaultPartitionSuffix;
|
private final String defaultPartitionSuffix;
|
||||||
|
|
||||||
private int port = 53389;
|
private int port = 53389;
|
||||||
|
|
||||||
|
@ -51,7 +52,7 @@ public class UnboundIdContainer
|
||||||
|
|
||||||
private boolean running;
|
private boolean running;
|
||||||
|
|
||||||
private String ldif;
|
private final String ldif;
|
||||||
|
|
||||||
public UnboundIdContainer(String defaultPartitionSuffix, String ldif) {
|
public UnboundIdContainer(String defaultPartitionSuffix, String ldif) {
|
||||||
this.defaultPartitionSuffix = defaultPartitionSuffix;
|
this.defaultPartitionSuffix = defaultPartitionSuffix;
|
||||||
|
@ -79,7 +80,7 @@ public class UnboundIdContainer
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
|
||||||
this.context = applicationContext;
|
this.context = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -129,7 +129,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
||||||
/**
|
/**
|
||||||
* The base DN from which the search for group membership should be performed
|
* The base DN from which the search for group membership should be performed
|
||||||
*/
|
*/
|
||||||
private String groupSearchBase;
|
private final String groupSearchBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The pattern to be used for the user search. {0} is the user's DN
|
* The pattern to be used for the user search. {0} is the user's DN
|
||||||
|
@ -166,7 +166,7 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
||||||
if (groupSearchBase == null) {
|
if (groupSearchBase == null) {
|
||||||
logger.info("Will not perform group search since groupSearchBase is null.");
|
logger.info("Will not perform group search since groupSearchBase is null.");
|
||||||
}
|
}
|
||||||
else if (groupSearchBase.length() == 0) {
|
else if (groupSearchBase.isEmpty()) {
|
||||||
logger.info("Will perform group search from the context source base since groupSearchBase is empty.");
|
logger.info("Will perform group search from the context source base since groupSearchBase is empty.");
|
||||||
}
|
}
|
||||||
this.authorityMapper = (record) -> {
|
this.authorityMapper = (record) -> {
|
||||||
|
@ -365,16 +365,6 @@ public class DefaultLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
|
||||||
return this.convertToUpperCase;
|
return this.convertToUpperCase;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the default role Method available so that classes extending this can
|
|
||||||
* override
|
|
||||||
* @return the default role used
|
|
||||||
* @see #setDefaultRole(String)
|
|
||||||
*/
|
|
||||||
private GrantedAuthority getDefaultRole() {
|
|
||||||
return this.defaultRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the search controls Method available so that classes extending this can
|
* Returns the search controls Method available so that classes extending this can
|
||||||
* override the search controls used
|
* override the search controls used
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2023 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -31,16 +31,16 @@ import org.springframework.util.Assert;
|
||||||
*/
|
*/
|
||||||
public class LdapAuthority implements GrantedAuthority {
|
public class LdapAuthority implements GrantedAuthority {
|
||||||
|
|
||||||
private String dn;
|
private final String dn;
|
||||||
|
|
||||||
private String role;
|
private final String role;
|
||||||
|
|
||||||
private Map<String, List<String>> attributes;
|
private final Map<String, List<String>> attributes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an LdapAuthority that has a role and a DN but no other attributes
|
* Constructs an LdapAuthority that has a role and a DN but no other attributes
|
||||||
* @param role
|
* @param role the principal's role
|
||||||
* @param dn
|
* @param dn the distinguished name
|
||||||
*/
|
*/
|
||||||
public LdapAuthority(String role, String dn) {
|
public LdapAuthority(String role, String dn) {
|
||||||
this(role, dn, null);
|
this(role, dn, null);
|
||||||
|
@ -48,9 +48,9 @@ public class LdapAuthority implements GrantedAuthority {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an LdapAuthority with the given role, DN and other LDAP attributes
|
* Constructs an LdapAuthority with the given role, DN and other LDAP attributes
|
||||||
* @param role
|
* @param role the principal's role
|
||||||
* @param dn
|
* @param dn the distinguished name
|
||||||
* @param attributes
|
* @param attributes additional LDAP attributes
|
||||||
*/
|
*/
|
||||||
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");
|
||||||
|
@ -70,7 +70,7 @@ public class LdapAuthority implements GrantedAuthority {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the DN for this LDAP authority
|
* Returns the DN for this LDAP authority
|
||||||
* @return
|
* @return the distinguished name
|
||||||
*/
|
*/
|
||||||
public String getDn() {
|
public String getDn() {
|
||||||
return this.dn;
|
return this.dn;
|
||||||
|
@ -91,7 +91,7 @@ public class LdapAuthority implements GrantedAuthority {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first attribute value for a specified attribute
|
* Returns the first attribute value for a specified attribute
|
||||||
* @param name
|
* @param name the attribute name
|
||||||
* @return the first attribute value for a specified attribute, may be null
|
* @return the first attribute value for a specified attribute, may be null
|
||||||
*/
|
*/
|
||||||
public String getFirstAttributeValue(String name) {
|
public String getFirstAttributeValue(String name) {
|
||||||
|
|
|
@ -44,7 +44,6 @@ import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.core.log.LogMessage;
|
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.ContextSource;
|
import org.springframework.ldap.core.ContextSource;
|
||||||
import org.springframework.ldap.core.DirContextAdapter;
|
import org.springframework.ldap.core.DirContextAdapter;
|
||||||
import org.springframework.ldap.core.DistinguishedName;
|
import org.springframework.ldap.core.DistinguishedName;
|
||||||
|
@ -121,7 +120,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
private final LdapTemplate template;
|
private final LdapTemplate template;
|
||||||
|
|
||||||
/** 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<GrantedAuthority> roleMapper = (attributes) -> {
|
||||||
Attribute roleAttr = attributes.get(this.groupRoleAttributeName);
|
Attribute roleAttr = attributes.get(this.groupRoleAttributeName);
|
||||||
NamingEnumeration<?> ne = roleAttr.getAll();
|
NamingEnumeration<?> ne = roleAttr.getAll();
|
||||||
Object group = ne.next();
|
Object group = ne.next();
|
||||||
|
@ -147,7 +146,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirContextAdapter loadUserAsContext(final LdapName dn, final String username) {
|
private DirContextAdapter loadUserAsContext(final LdapName dn, final String username) {
|
||||||
return (DirContextAdapter) this.template.executeReadOnly((ContextExecutor) (ctx) -> {
|
return this.template.executeReadOnly((ctx) -> {
|
||||||
try {
|
try {
|
||||||
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));
|
||||||
|
@ -162,6 +161,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
* Changes the password for the current user. The username is obtained from the
|
* Changes the password for the current user. The username is obtained from the
|
||||||
* security context.
|
* security context.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* There are two supported strategies for modifying the user's password depending on
|
* There are two supported strategies for modifying the user's password depending on
|
||||||
* the capabilities of the corresponding LDAP server.
|
* the capabilities of the corresponding LDAP server.
|
||||||
*
|
*
|
||||||
|
@ -170,6 +170,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062"> LDAP Password Modify
|
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062"> LDAP Password Modify
|
||||||
* Extended Operation </a>.
|
* Extended Operation </a>.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* See {@link LdapUserDetailsManager#setUsePasswordModifyExtensionOperation(boolean)}
|
* See {@link LdapUserDetailsManager#setUsePasswordModifyExtensionOperation(boolean)}
|
||||||
* for details.
|
* for details.
|
||||||
* </p>
|
* </p>
|
||||||
|
@ -205,7 +206,6 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
* @param username the user whose roles are required.
|
* @param username the user whose roles are required.
|
||||||
* @return the granted authorities returned by the group search
|
* @return the granted authorities returned by the group search
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
List<GrantedAuthority> getUserAuthorities(final LdapName dn, final String username) {
|
List<GrantedAuthority> getUserAuthorities(final LdapName dn, final String username) {
|
||||||
SearchExecutor se = (ctx) -> {
|
SearchExecutor se = (ctx) -> {
|
||||||
LdapName fullDn = LdapUtils.getFullDn(dn, ctx);
|
LdapName fullDn = LdapUtils.getFullDn(dn, ctx);
|
||||||
|
@ -214,7 +214,8 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
return ctx.search(this.groupSearchBase, this.groupSearchFilter,
|
return ctx.search(this.groupSearchBase, this.groupSearchFilter,
|
||||||
new String[] { fullDn.toString(), username }, ctrls);
|
new String[] { fullDn.toString(), username }, ctrls);
|
||||||
};
|
};
|
||||||
AttributesMapperCallbackHandler roleCollector = new AttributesMapperCallbackHandler(this.roleMapper);
|
AttributesMapperCallbackHandler<GrantedAuthority> roleCollector = new AttributesMapperCallbackHandler<>(
|
||||||
|
this.roleMapper);
|
||||||
this.template.search(se, roleCollector);
|
this.template.search(se, roleCollector);
|
||||||
return roleCollector.getList();
|
return roleCollector.getList();
|
||||||
}
|
}
|
||||||
|
@ -229,7 +230,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
// 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 them
|
// DN and remove them
|
||||||
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
|
List<GrantedAuthority> authorities = getUserAuthorities(dn, user.getUsername());
|
||||||
if (authorities.size() > 0) {
|
if (!authorities.isEmpty()) {
|
||||||
removeAuthorities(dn, authorities);
|
removeAuthorities(dn, authorities);
|
||||||
}
|
}
|
||||||
addAuthorities(dn, user.getAuthorities());
|
addAuthorities(dn, user.getAuthorities());
|
||||||
|
@ -322,7 +323,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
|
|
||||||
private void modifyAuthorities(final LdapName userDn, final Collection<? extends GrantedAuthority> authorities,
|
private void modifyAuthorities(final LdapName userDn, final Collection<? extends GrantedAuthority> authorities,
|
||||||
final int modType) {
|
final int modType) {
|
||||||
this.template.executeReadWrite((ContextExecutor) (ctx) -> {
|
this.template.executeReadWrite((ctx) -> {
|
||||||
for (GrantedAuthority authority : authorities) {
|
for (GrantedAuthority authority : authorities) {
|
||||||
String group = convertAuthorityToGroup(authority);
|
String group = convertAuthorityToGroup(authority);
|
||||||
LdapName fullDn = LdapUtils.getFullDn(userDn, ctx);
|
LdapName fullDn = LdapUtils.getFullDn(userDn, ctx);
|
||||||
|
@ -389,20 +390,26 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
/**
|
/**
|
||||||
* Sets the method by which a user's password gets modified.
|
* Sets the method by which a user's password gets modified.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* If set to {@code true}, then {@link LdapUserDetailsManager#changePassword} will
|
* If set to {@code true}, then {@link LdapUserDetailsManager#changePassword} will
|
||||||
* modify the user's password by way of the
|
* modify the user's password by way of the
|
||||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062">Password Modify
|
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062">Password Modify
|
||||||
* Extension Operation</a>.
|
* Extension Operation</a>.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* If set to {@code false}, then {@link LdapUserDetailsManager#changePassword} will
|
* If set to {@code false}, then {@link LdapUserDetailsManager#changePassword} will
|
||||||
* modify the user's password by directly modifying attributes on the corresponding
|
* modify the user's password by directly modifying attributes on the corresponding
|
||||||
* entry.
|
* entry.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* Before using this setting, ensure that the corresponding LDAP server supports this
|
* Before using this setting, ensure that the corresponding LDAP server supports this
|
||||||
* extended operation.
|
* extended operation.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* By default, {@code usePasswordModifyExtensionOperation} is false.
|
* By default, {@code usePasswordModifyExtensionOperation} is false.
|
||||||
* @param usePasswordModifyExtensionOperation
|
* @param usePasswordModifyExtensionOperation whether to use the
|
||||||
|
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062">Password Modify
|
||||||
|
* Extension Operation</a> to modify the password
|
||||||
* @since 4.2.9
|
* @since 4.2.9
|
||||||
*/
|
*/
|
||||||
public void setUsePasswordModifyExtensionOperation(boolean usePasswordModifyExtensionOperation) {
|
public void setUsePasswordModifyExtensionOperation(boolean usePasswordModifyExtensionOperation) {
|
||||||
|
@ -473,6 +480,7 @@ public class LdapUserDetailsManager implements UserDetailsManager {
|
||||||
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062"> LDAP Password Modify
|
* <a target="_blank" href="https://tools.ietf.org/html/rfc3062"> LDAP Password Modify
|
||||||
* Extended Operation </a> client request.
|
* Extended Operation </a> client request.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
* Can be directed at any LDAP server that supports the Password Modify Extended
|
* Can be directed at any LDAP server that supports the Password Modify Extended
|
||||||
* Operation.
|
* Operation.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2014 the original author or authors.
|
* Copyright 2002-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -176,7 +176,7 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
||||||
if (getAttributeNames() == null) {
|
if (getAttributeNames() == null) {
|
||||||
setAttributeNames(new HashSet<>());
|
setAttributeNames(new HashSet<>());
|
||||||
}
|
}
|
||||||
if (StringUtils.hasText(getGroupRoleAttribute()) && !getAttributeNames().contains(getGroupRoleAttribute())) {
|
if (StringUtils.hasText(getGroupRoleAttribute())) {
|
||||||
getAttributeNames().add(getGroupRoleAttribute());
|
getAttributeNames().add(getGroupRoleAttribute());
|
||||||
}
|
}
|
||||||
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
|
Set<Map<String, List<String>>> userRoles = getLdapTemplate().searchForMultipleAttributeValues(
|
||||||
|
@ -200,7 +200,7 @@ public class NestedLdapAuthoritiesPopulator extends DefaultLdapAuthoritiesPopula
|
||||||
// this prevents a forever loop for a misconfigured ldap directory
|
// this prevents a forever loop for a misconfigured ldap directory
|
||||||
circular = circular | (!authorities.add(new LdapAuthority(role, dn, record)));
|
circular = circular | (!authorities.add(new LdapAuthority(role, dn, record)));
|
||||||
}
|
}
|
||||||
String roleName = (roles.size() > 0) ? roles.iterator().next() : dn;
|
String roleName = (!roles.isEmpty()) ? roles.iterator().next() : dn;
|
||||||
if (!circular) {
|
if (!circular) {
|
||||||
performNestedSearch(dn, roleName, authorities, (depth - 1));
|
performNestedSearch(dn, roleName, authorities, (depth - 1));
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ public interface UserDetailsContextMapper {
|
||||||
* Creates a fully populated UserDetails object for use by the security framework.
|
* Creates a fully populated UserDetails object for use by the security framework.
|
||||||
* @param ctx the context object which contains the user information.
|
* @param ctx the context object which contains the user information.
|
||||||
* @param username the user's supplied login name.
|
* @param username the user's supplied login name.
|
||||||
* @param authorities
|
* @param authorities the authorities to add to the {@code UserDetails} instance
|
||||||
* @return the user object.
|
* @return the user object.
|
||||||
*/
|
*/
|
||||||
UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
UserDetails mapUserFromContext(DirContextOperations ctx, String username,
|
||||||
|
|
Loading…
Reference in New Issue