Add @LdapEncode annotation to configure encoding of LDAP parameters.
Closes #509 Original pull request: #518
This commit is contained in:
committed by
Mark Paluch
parent
c9a45fb33f
commit
de3b8ffc5f
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.ldap.repository;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
|
||||
/**
|
||||
* Allows passing of custom {@link LdapEncoder}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.5.0
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface LdapEncode {
|
||||
|
||||
/**
|
||||
* {@link LdapEncoder} to instantiate to encode query parameters.
|
||||
*
|
||||
* @return {@link LdapEncoder} class
|
||||
*/
|
||||
@AliasFor("encoder")
|
||||
Class<? extends LdapEncoder> value();
|
||||
|
||||
/**
|
||||
* {@link LdapEncoder} to instantiate to encode query parameters.
|
||||
*
|
||||
* @return {@link LdapEncoder} class
|
||||
*/
|
||||
@AliasFor("value")
|
||||
Class<? extends LdapEncoder> encoder() default LdapEncoder.class;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.ldap.repository;
|
||||
|
||||
/**
|
||||
* Allows plugging in custom encoding for {@link LdapEncode}.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public interface LdapEncoder {
|
||||
|
||||
/**
|
||||
* Escape a value for use in a filter.
|
||||
* @param value the value to escape.
|
||||
* @return a properly escaped representation of the supplied value.
|
||||
*/
|
||||
String filterEncode(String value);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.ldap.repository.query;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.geo.Distance;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.ParametersSource;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* Custom extension of {@link Parameters} discovering additional
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
* @since 3.5.0
|
||||
*/
|
||||
public class LdapParameters extends Parameters<LdapParameters, LdapParameters.LdapParameter> {
|
||||
|
||||
private final TypeInformation<?> domainType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LdapParameters} instance from the given {@link Method} and {@link LdapQueryMethod}.
|
||||
*
|
||||
* @param parametersSource must not be {@literal null}.
|
||||
*/
|
||||
public LdapParameters(ParametersSource parametersSource) {
|
||||
|
||||
super(parametersSource, methodParameter -> new LdapParameter(methodParameter,
|
||||
parametersSource.getDomainTypeInformation()));
|
||||
|
||||
this.domainType = parametersSource.getDomainTypeInformation();
|
||||
}
|
||||
|
||||
private LdapParameters(List<LdapParameter> parameters, TypeInformation<?> domainType) {
|
||||
|
||||
super(parameters);
|
||||
this.domainType = domainType;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LdapParameters createFrom(List<LdapParameter> parameters) {
|
||||
return new LdapParameters(parameters, this.domainType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Custom {@link Parameter} implementation adding parameters of type {@link Distance} to the special ones.
|
||||
*
|
||||
* @author Marcin Grzejszczak
|
||||
*/
|
||||
static class LdapParameter extends Parameter {
|
||||
|
||||
final MethodParameter parameter;
|
||||
|
||||
/**
|
||||
* Creates a new {@link LdapParameter}.
|
||||
*
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
*/
|
||||
LdapParameter(MethodParameter parameter, TypeInformation<?> domainType) {
|
||||
super(parameter, domainType);
|
||||
this.parameter = parameter;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.ldap.repository.Query;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.ParametersSource;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -50,6 +49,16 @@ public class LdapQueryMethod extends QueryMethod {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LdapParameters createParameters(ParametersSource parametersSource) {
|
||||
return new LdapParameters(parametersSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LdapParameters getParameters() {
|
||||
return (LdapParameters) super.getParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the target method is annotated with {@link org.springframework.data.ldap.repository.Query}.
|
||||
*
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.data.ldap.repository.query;
|
||||
|
||||
import static org.springframework.data.ldap.repository.query.StringBasedQuery.BindingContext.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -27,8 +25,10 @@ import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.expression.ValueExpression;
|
||||
import org.springframework.data.expression.ValueExpressionParser;
|
||||
import org.springframework.data.ldap.repository.LdapEncode;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
@@ -39,6 +39,8 @@ import org.springframework.ldap.support.LdapEncoder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.springframework.data.ldap.repository.query.StringBasedQuery.BindingContext.ParameterBinding;
|
||||
|
||||
/**
|
||||
* String-based Query abstracting a query with parameter bindings.
|
||||
*
|
||||
@@ -48,7 +50,7 @@ import org.springframework.util.StringUtils;
|
||||
class StringBasedQuery {
|
||||
|
||||
private final String query;
|
||||
private final Parameters<?, ?> parameters;
|
||||
private final LdapParameters parameters;
|
||||
private final List<ParameterBinding> queryParameterBindings = new ArrayList<>();
|
||||
private final ExpressionDependencies expressionDependencies;
|
||||
|
||||
@@ -59,7 +61,7 @@ class StringBasedQuery {
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @param expressionParser must not be {@literal null}.
|
||||
*/
|
||||
public StringBasedQuery(String query, Parameters<?, ?> parameters, ValueExpressionDelegate expressionParser) {
|
||||
public StringBasedQuery(String query, LdapParameters parameters, ValueExpressionDelegate expressionParser) {
|
||||
|
||||
this.query = ParameterBindingParser.parseAndCollectParameterBindingsFromQueryIntoBindings(query,
|
||||
this.queryParameterBindings, expressionParser);
|
||||
@@ -298,7 +300,7 @@ class StringBasedQuery {
|
||||
*/
|
||||
static class BindingContext {
|
||||
|
||||
private final Parameters<?, ?> parameters;
|
||||
private final LdapParameters parameters;
|
||||
private final ParameterAccessor parameterAccessor;
|
||||
private final List<ParameterBinding> bindings;
|
||||
private final Function<ValueExpression, Object> evaluator;
|
||||
@@ -306,7 +308,7 @@ class StringBasedQuery {
|
||||
/**
|
||||
* Create new {@link BindingContext}.
|
||||
*/
|
||||
BindingContext(Parameters<?, ?> parameters, ParameterAccessor parameterAccessor, List<ParameterBinding> bindings,
|
||||
BindingContext(LdapParameters parameters, ParameterAccessor parameterAccessor, List<ParameterBinding> bindings,
|
||||
Function<ValueExpression, Object> evaluator) {
|
||||
|
||||
this.parameters = parameters;
|
||||
@@ -356,11 +358,15 @@ class StringBasedQuery {
|
||||
if (binding.isExpression()) {
|
||||
return evaluator.apply(binding.getRequiredExpression());
|
||||
}
|
||||
|
||||
Object value = binding.isNamed()
|
||||
? parameterAccessor.getBindableValue(getParameterIndex(parameters, binding.getRequiredParameterName()))
|
||||
: parameterAccessor.getBindableValue(binding.getParameterIndex());
|
||||
return value == null ? null : LdapEncoder.filterEncode(value.toString());
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return binding.getEncodedValue(parameters, value);
|
||||
}
|
||||
|
||||
private int getParameterIndex(Parameters<?, ?> parameters, String parameterName) {
|
||||
@@ -407,6 +413,38 @@ class StringBasedQuery {
|
||||
return new ParameterBinding(-1, null, name);
|
||||
}
|
||||
|
||||
Object getEncodedValue(LdapParameters ldapParameters, Object value) {
|
||||
org.springframework.data.ldap.repository.LdapEncoder encoder = encoderForParameter(ldapParameters);
|
||||
if (encoder == null) {
|
||||
return LdapEncoder.filterEncode(value.toString());
|
||||
}
|
||||
return encoder.filterEncode(value.toString());
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
org.springframework.data.ldap.repository.LdapEncoder encoderForParameter(LdapParameters ldapParameters) {
|
||||
for (LdapParameters.LdapParameter parameter : ldapParameters) {
|
||||
if (isByName(parameter) || isByIndex(parameter)) {
|
||||
LdapEncode ldapEncode = parameter.parameter.getParameterAnnotation(LdapEncode.class);
|
||||
if (ldapEncode == null) {
|
||||
return null;
|
||||
}
|
||||
Class<? extends org.springframework.data.ldap.repository.LdapEncoder> encoder = ldapEncode.value();
|
||||
return BeanUtils.instantiateClass(encoder);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isByIndex(LdapParameters.LdapParameter parameter) {
|
||||
return parameterIndex != -1 && parameter.getIndex() == parameterIndex;
|
||||
}
|
||||
|
||||
private boolean isByName(LdapParameters.LdapParameter parameter) {
|
||||
return parameterName != null && parameterName.equals(parameter.getName().orElse(null));
|
||||
}
|
||||
|
||||
boolean isNamed() {
|
||||
return (parameterName != null);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
*/
|
||||
package org.springframework.data.ldap.repository.query;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.data.ldap.core.mapping.LdapMappingContext;
|
||||
import org.springframework.data.ldap.repository.LdapEncoder;
|
||||
import org.springframework.data.ldap.repository.LdapEncode;
|
||||
import org.springframework.data.ldap.repository.LdapRepository;
|
||||
import org.springframework.data.ldap.repository.Query;
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
@@ -32,6 +32,8 @@ import org.springframework.data.repository.query.ValueExpressionDelegate;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.query.LdapQuery;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AnnotatedLdapRepositoryQuery}
|
||||
*
|
||||
@@ -79,6 +81,18 @@ class AnnotatedLdapRepositoryQueryUnitTests {
|
||||
assertThat(ldapQuery.base()).hasToString("cn=John\\29");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncodeWithCustomEncoder() throws NoSuchMethodException {
|
||||
|
||||
LdapQueryMethod method = queryMethod("customEncoder", String.class);
|
||||
AnnotatedLdapRepositoryQuery query = repositoryQuery(method);
|
||||
|
||||
LdapQuery ldapQuery = query.createQuery(
|
||||
new LdapParametersParameterAccessor(method, new Object[] { "Doe" }));
|
||||
|
||||
assertThat(ldapQuery.filter().encode()).isEqualTo("(cn=Doebar)");
|
||||
}
|
||||
|
||||
private LdapQueryMethod queryMethod(String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
|
||||
return new LdapQueryMethod(QueryRepository.class.getMethod(methodName, parameterTypes),
|
||||
new DefaultRepositoryMetadata(QueryRepository.class), new SpelAwareProxyProjectionFactory());
|
||||
@@ -100,5 +114,16 @@ class AnnotatedLdapRepositoryQueryUnitTests {
|
||||
@Query(base = ":dc", value = "(cn=:fullName)")
|
||||
List<SchemaEntry> baseNamedParameters(String fullName, String dc);
|
||||
|
||||
@Query(value = "(cn=:fullName)")
|
||||
List<SchemaEntry> customEncoder(@LdapEncode(MyEncoder.class) String fullName);
|
||||
|
||||
}
|
||||
|
||||
static class MyEncoder implements LdapEncoder {
|
||||
|
||||
@Override
|
||||
public String filterEncode(String value) {
|
||||
return value + "bar";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user