Skip to content

Commit

Permalink
Add @LdapEncode annotation to configure encoding of LDAP parameters.
Browse files Browse the repository at this point in the history
Closes #509
Original pull request: #518
  • Loading branch information
marcingrzejszczak authored and mp911de committed Nov 18, 2024
1 parent c9a45fb commit de3b8ff
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
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;
Expand Down Expand Up @@ -50,6 +49,16 @@ public LdapQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFac
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}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,8 +25,10 @@
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;
Expand All @@ -39,6 +39,8 @@
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.
*
Expand All @@ -48,7 +50,7 @@
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;

Expand All @@ -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);
Expand Down Expand Up @@ -298,15 +300,15 @@ public static String bind(String input, List<Object> parameters) {
*/
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;

/**
* 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;
Expand Down Expand Up @@ -356,11 +358,15 @@ private Object getParameterValueForBinding(ParameterBinding binding) {
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) {
Expand Down Expand Up @@ -407,6 +413,38 @@ static ParameterBinding named(String name) {
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,6 +32,8 @@
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}
*
Expand Down Expand Up @@ -79,6 +81,18 @@ void shouldEncodeBase() throws NoSuchMethodException {
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());
Expand All @@ -100,5 +114,16 @@ interface QueryRepository extends LdapRepository<SchemaEntry> {
@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";
}
}
}

0 comments on commit de3b8ff

Please sign in to comment.