blob: 34f40621a0c11242465d4c00eb712ce5a5698fb5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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
*
* http://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.apache.geode.cache.query.internal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.stream.IntStream;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.apache.geode.cache.EntryDestroyedException;
import org.apache.geode.cache.query.NameNotFoundException;
import org.apache.geode.cache.query.NameResolutionException;
import org.apache.geode.cache.query.QueryInvocationTargetException;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.security.MethodInvocationAuthorizer;
import org.apache.geode.cache.query.security.RestrictedMethodAuthorizer;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.Token;
import org.apache.geode.pdx.internal.TypeRegistry;
import org.apache.geode.security.NotAuthorizedException;
@RunWith(JUnitParamsRunner.class)
public class AttributeDescriptorTest {
private static final String PUBLIC_NO_ACCESSORS = "publicAttributeWithoutAccessors";
private static final String PUBLIC_ACCESSOR_BY_NAME = "publicAttributeWithPublicAccessor";
private static final String PUBLIC_ACCESSOR_BY_GETTER = "publicAttributeWithPublicGetterMethod";
private static final String PRIVATE_ACCESSOR_BY_NAME = "nonPublicAttributeWithPublicAccessor";
private static final String PRIVATE_ACCESSOR_BY_GETTER =
"nonPublicAttributeWithPublicGetterMethod";
private TestBean testBean;
private TypeRegistry typeRegistry;
private QueryExecutionContext queryExecutionContext;
private MethodInvocationAuthorizer methodInvocationAuthorizer;
@Before
public void setUp() {
AttributeDescriptor._localCache.clear();
testBean = new TestBean(PUBLIC_NO_ACCESSORS, PUBLIC_ACCESSOR_BY_NAME, PUBLIC_ACCESSOR_BY_GETTER,
PRIVATE_ACCESSOR_BY_NAME, PRIVATE_ACCESSOR_BY_GETTER);
InternalCache mockCache = mock(InternalCache.class);
typeRegistry = new TypeRegistry(mockCache, true);
methodInvocationAuthorizer = spy(MethodInvocationAuthorizer.class);
InternalQueryService mockQueryService = mock(InternalQueryService.class);
when(mockCache.getQueryService()).thenReturn(mockQueryService);
when(mockQueryService.getMethodInvocationAuthorizer()).thenReturn(methodInvocationAuthorizer);
queryExecutionContext = new QueryExecutionContext(null, mockCache);
}
@Test
@Parameters({PUBLIC_NO_ACCESSORS, PUBLIC_ACCESSOR_BY_NAME, PUBLIC_ACCESSOR_BY_GETTER,
PRIVATE_ACCESSOR_BY_NAME, PRIVATE_ACCESSOR_BY_GETTER})
public void validateReadTypeShouldReturnTrueWhenMemberCanBeFound(String attributeName) {
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
assertThat(attributeDescriptor.validateReadType(TestBean.class)).isTrue();
}
@Test
public void validateReadTypeShouldReturnFalseWhenMemberCanNotBeFound() {
AttributeDescriptor attributeDescriptor =
new AttributeDescriptor(typeRegistry, "nonExistingAttribute");
assertThat(attributeDescriptor.validateReadType(TestBean.class)).isFalse();
}
@Test
@Parameters({PRIVATE_ACCESSOR_BY_NAME, PRIVATE_ACCESSOR_BY_GETTER})
public void getReadFieldShouldReturnNullForNonPublicAttributes(String attributeName) {
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
assertThat(attributeDescriptor.getReadField(TestBean.class)).isNull();
}
@Test
@Parameters({PUBLIC_NO_ACCESSORS, PUBLIC_ACCESSOR_BY_NAME, PUBLIC_ACCESSOR_BY_GETTER})
public void getReadFieldShouldReturnRequestedFieldForPublicAttributes(String attributeName)
throws NoSuchFieldException {
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
assertThat(attributeDescriptor.getReadField(TestBean.class))
.isEqualTo(TestBean.class.getField(attributeName));
}
@Test
@Parameters({PUBLIC_ACCESSOR_BY_GETTER, PRIVATE_ACCESSOR_BY_GETTER})
public void getReadMethodShouldReturnRequestedMethodForAttributesWithGetters(String attributeName)
throws NoSuchMethodException {
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
String getterName = "get" + attributeName.substring(0, 1).toUpperCase()
+ attributeName.substring(1);
assertThat(attributeDescriptor.getReadMethod(TestBean.class))
.isEqualTo(TestBean.class.getMethod(getterName));
}
@Test
@Parameters({PUBLIC_ACCESSOR_BY_NAME, PRIVATE_ACCESSOR_BY_NAME})
public void getReadMethodShouldReturnRequestedMethodForAttributesWithAccessors(
String attributeName) throws NoSuchMethodException {
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
assertThat(attributeDescriptor.getReadMethod(TestBean.class, attributeName))
.isEqualTo(TestBean.class.getMethod(attributeName));
}
@Test
public void getReadMethodShouldReturnNullAndUpdateCachedClassToMethodsMapWhenMethodCanNotBeFound() {
DefaultQuery.getPdxClasstoMethodsmap().clear();
AttributeDescriptor attributeDescriptor =
new AttributeDescriptor(typeRegistry, "nonExistingAttribute");
assertThat(attributeDescriptor.getReadMethod(TestBean.class)).isNull();
assertThat(DefaultQuery.getPdxClasstoMethodsmap().isEmpty()).isFalse();
assertThat(DefaultQuery.getPdxClasstoMethodsmap()
.containsKey(TestBean.class.getCanonicalName())).isTrue();
assertThat(DefaultQuery.getPdxClasstoMethodsmap().get(TestBean.class.getCanonicalName())
.contains("nonExistingAttribute")).isTrue();
}
@Test
@Parameters({PUBLIC_NO_ACCESSORS, PUBLIC_ACCESSOR_BY_NAME, PUBLIC_ACCESSOR_BY_GETTER})
public void getReadMemberShouldReturnFieldWhenAttributeIsPublicAndUseInternalCache(
String attributeName) throws NameNotFoundException {
AttributeDescriptor attributeDescriptor =
spy(new AttributeDescriptor(typeRegistry, attributeName));
assertThat(attributeDescriptor.getReadMember(TestBean.class)).isInstanceOf(Field.class);
assertThat(attributeDescriptor.getReadMember(TestBean.class)).isInstanceOf(Field.class);
// Second time the field should be obtained from the local cache.
verify(attributeDescriptor, times(1)).getReadField(TestBean.class);
}
@Test
@Parameters({PRIVATE_ACCESSOR_BY_NAME, PRIVATE_ACCESSOR_BY_GETTER})
public void getReadMemberShouldReturnMethodWhenAttributeIsNotPublicAndUseInternalCache(
String attributeName) throws NameNotFoundException {
AttributeDescriptor attributeDescriptor =
spy(new AttributeDescriptor(typeRegistry, attributeName));
assertThat(attributeDescriptor.getReadMember(TestBean.class)).isInstanceOf(Method.class);
assertThat(attributeDescriptor.getReadMember(TestBean.class)).isInstanceOf(Method.class);
// Second time the field should be obtained from the local cache.
verify(attributeDescriptor, times(1)).getReadMethod(TestBean.class);
}
@Test
public void getReadMemberShouldThrowExceptionWhenMethodCanNotBeFound() {
AttributeDescriptor attributeDescriptor =
new AttributeDescriptor(typeRegistry, "nonExistingAttribute");
assertThatThrownBy(() -> attributeDescriptor.getReadMember(TestBean.class))
.isInstanceOf(NameNotFoundException.class)
.hasMessageContaining(
"No public attribute named ' nonExistingAttribute ' was found in class "
+ TestBean.class.getName());
}
@Test
public void readReflectionShouldReturnUndefinedWhenTargetObjectIsAnInternalToken()
throws NameNotFoundException, QueryInvocationTargetException {
AttributeDescriptor attributeDescriptor =
new AttributeDescriptor(typeRegistry, "nonExistingAttribute");
assertThat(attributeDescriptor.readReflection(mock(Token.class), queryExecutionContext))
.isEqualTo(QueryService.UNDEFINED);
}
@Test
public void readReflectionShouldThrowExceptionWhenMemberCanNotBeFound() {
AttributeDescriptor attributeDescriptor =
new AttributeDescriptor(typeRegistry, "nonExistingAttribute");
assertThatThrownBy(
() -> attributeDescriptor.readReflection(TestBean.class, queryExecutionContext))
.isInstanceOf(NameNotFoundException.class);
}
@Test
public void readReflectionShouldReturnUndefinedWhenEntryDestroyedExceptionIsThrown()
throws NameNotFoundException, QueryInvocationTargetException {
doReturn(true).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor attributeDescriptor =
spy(new AttributeDescriptor(typeRegistry, "throwEntryDestroyedExceptionMethod"));
assertThat(attributeDescriptor.readReflection(testBean, queryExecutionContext))
.isEqualTo(QueryService.UNDEFINED);
}
@Test
@Parameters({PUBLIC_NO_ACCESSORS, PUBLIC_ACCESSOR_BY_NAME, PUBLIC_ACCESSOR_BY_GETTER})
public void readReflectionForPublicAttributeShouldNotInvokeAuthorizer(String attributeName)
throws NameResolutionException, QueryInvocationTargetException {
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
Object result = attributeDescriptor.readReflection(testBean, queryExecutionContext);
assertThat(result).isInstanceOf(String.class);
assertThat(result).isEqualTo(attributeName);
verify(methodInvocationAuthorizer, never()).authorize(any(), any());
}
@Test
@Parameters({PRIVATE_ACCESSOR_BY_NAME, PRIVATE_ACCESSOR_BY_GETTER})
public void readReflectionForImplicitMethodInvocationShouldReturnCorrectlyWhenMethodIsAuthorized(
String attributeName) throws NameResolutionException, QueryInvocationTargetException {
doReturn(true).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
Object result = attributeDescriptor.readReflection(testBean, queryExecutionContext);
assertThat(result).isInstanceOf(String.class);
assertThat(result).isEqualTo(attributeName);
}
@Test
@Parameters({
"nonPublicAttributeWithPublicAccessor, nonPublicAttributeWithPublicAccessor",
"nonPublicAttributeWithPublicGetterMethod, getNonPublicAttributeWithPublicGetterMethod"})
public void readReflectionForImplicitMethodInvocationShouldReturnCorrectlyWhenMethodIsAuthorizedAndCacheResult(
String attributeName, String methodName) throws Exception {
doReturn(true).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
attributeDescriptor.readReflection(testBean, queryExecutionContext);
String cacheKey = TestBean.class.getCanonicalName() + "." + methodName;
assertThat(queryExecutionContext.cacheGet(cacheKey)).isEqualTo(true);
}
@Test
public void readReflectionForImplicitMethodInvocationShouldUseCachedAuthorizerResultWhenMethodIsAuthorizedAndQueryContextIsTheSame() {
doReturn(true).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor nonPublicAttributeWithPublicAccessor =
new AttributeDescriptor(typeRegistry, PRIVATE_ACCESSOR_BY_NAME);
AttributeDescriptor nonPublicAttributeWithPublicGetterMethod =
new AttributeDescriptor(typeRegistry, PRIVATE_ACCESSOR_BY_GETTER);
// Same QueryExecutionContext -> Use cache.
IntStream.range(0, 10).forEach(element -> {
try {
assertThat(
nonPublicAttributeWithPublicAccessor.readReflection(testBean, queryExecutionContext))
.isInstanceOf(String.class).isEqualTo(PRIVATE_ACCESSOR_BY_NAME);
assertThat(nonPublicAttributeWithPublicGetterMethod.readReflection(testBean,
queryExecutionContext)).isInstanceOf(String.class)
.isEqualTo(PRIVATE_ACCESSOR_BY_GETTER);
} catch (NameNotFoundException | QueryInvocationTargetException exception) {
throw new RuntimeException(exception);
}
});
verify(methodInvocationAuthorizer, times(2)).authorize(any(), any());
}
@Test
public void readReflectionForImplicitMethodInvocationShouldNotUseCachedAuthorizerResultWhenMethodIsAuthorizedAndQueryContextIsNotTheSame() {
doReturn(true).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor nonPublicAttributeWithPublicAccessor =
new AttributeDescriptor(typeRegistry, PRIVATE_ACCESSOR_BY_NAME);
AttributeDescriptor nonPublicAttributeWithPublicGetterMethod =
new AttributeDescriptor(typeRegistry, PRIVATE_ACCESSOR_BY_GETTER);
// New QueryExecutionContext every time -> Do not use cache.
IntStream.range(0, 10).forEach(element -> {
try {
QueryExecutionContext mockContext = mock(QueryExecutionContext.class);
when(mockContext.getMethodInvocationAuthorizer()).thenReturn(methodInvocationAuthorizer);
assertThat(nonPublicAttributeWithPublicAccessor.readReflection(testBean, mockContext))
.isInstanceOf(String.class).isEqualTo(PRIVATE_ACCESSOR_BY_NAME);
assertThat(nonPublicAttributeWithPublicGetterMethod.readReflection(testBean, mockContext))
.isInstanceOf(String.class).isEqualTo(PRIVATE_ACCESSOR_BY_GETTER);
} catch (NameNotFoundException | QueryInvocationTargetException exception) {
throw new RuntimeException(exception);
}
});
verify(methodInvocationAuthorizer, times(20)).authorize(any(), any());
}
@Test
@Parameters({PRIVATE_ACCESSOR_BY_NAME, PRIVATE_ACCESSOR_BY_GETTER})
public void readReflectionForImplicitMethodInvocationShouldThrowExceptionWhenMethodIsNotAuthorized(
String attributeName) {
doReturn(false).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
assertThatThrownBy(() -> attributeDescriptor.readReflection(testBean, queryExecutionContext))
.isInstanceOf(NotAuthorizedException.class)
.hasMessageStartingWith(RestrictedMethodAuthorizer.UNAUTHORIZED_STRING);
}
@Test
@Parameters({
"nonPublicAttributeWithPublicAccessor, nonPublicAttributeWithPublicAccessor",
"nonPublicAttributeWithPublicGetterMethod, getNonPublicAttributeWithPublicGetterMethod"})
public void readReflectionForImplicitMethodInvocationShouldShouldThrowExceptionWhenMethodIsNotAuthorizedAndCacheResult(
String attributeName, String methodName) {
doReturn(false).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
assertThatThrownBy(() -> attributeDescriptor.readReflection(testBean, queryExecutionContext))
.isInstanceOf(NotAuthorizedException.class)
.hasMessageStartingWith(RestrictedMethodAuthorizer.UNAUTHORIZED_STRING);
String cacheKey = TestBean.class.getCanonicalName() + "." + methodName;
assertThat(queryExecutionContext.cacheGet(cacheKey)).isEqualTo(false);
}
@Test
public void readReflectionForImplicitMethodInvocationShouldUseCachedAuthorizerResultWhenMethodIsForbiddenAndQueryContextIsTheSame() {
doReturn(false).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor nonPublicAttributeWithPublicAccessor =
new AttributeDescriptor(typeRegistry, PRIVATE_ACCESSOR_BY_NAME);
AttributeDescriptor nonPublicAttributeWithPublicGetterMethod =
new AttributeDescriptor(typeRegistry, PRIVATE_ACCESSOR_BY_GETTER);
// Same QueryExecutionContext -> Use cache.
IntStream.range(0, 10).forEach(element -> {
assertThatThrownBy(() -> nonPublicAttributeWithPublicAccessor.readReflection(testBean,
queryExecutionContext)).isInstanceOf(NotAuthorizedException.class)
.hasMessageStartingWith(RestrictedMethodAuthorizer.UNAUTHORIZED_STRING);
assertThatThrownBy(() -> nonPublicAttributeWithPublicGetterMethod.readReflection(testBean,
queryExecutionContext)).isInstanceOf(NotAuthorizedException.class)
.hasMessageStartingWith(RestrictedMethodAuthorizer.UNAUTHORIZED_STRING);
});
verify(methodInvocationAuthorizer, times(2)).authorize(any(), any());
}
@Test
public void readReflectionForImplicitMethodInvocationShouldNotUseCachedAuthorizerResultWhenMethodIsForbiddenAndQueryContextIsNotTheSame() {
doReturn(false).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor nonPublicAttributeWithPublicAccessor =
new AttributeDescriptor(typeRegistry, PRIVATE_ACCESSOR_BY_NAME);
AttributeDescriptor nonPublicAttributeWithPublicGetterMethod =
new AttributeDescriptor(typeRegistry, PRIVATE_ACCESSOR_BY_GETTER);
// New QueryExecutionContext every time -> Do not use cache.
IntStream.range(0, 10).forEach(element -> {
QueryExecutionContext mockContext = mock(QueryExecutionContext.class);
when(mockContext.getMethodInvocationAuthorizer()).thenReturn(methodInvocationAuthorizer);
assertThatThrownBy(
() -> nonPublicAttributeWithPublicAccessor.readReflection(testBean, mockContext))
.isInstanceOf(NotAuthorizedException.class)
.hasMessageStartingWith(RestrictedMethodAuthorizer.UNAUTHORIZED_STRING);
assertThatThrownBy(
() -> nonPublicAttributeWithPublicGetterMethod.readReflection(testBean, mockContext))
.isInstanceOf(NotAuthorizedException.class)
.hasMessageStartingWith(RestrictedMethodAuthorizer.UNAUTHORIZED_STRING);
});
verify(methodInvocationAuthorizer, times(20)).authorize(any(), any());
}
@Test
public void readShouldReturnUndefinedForNullOrUndefinedTargetObject()
throws NameNotFoundException, QueryInvocationTargetException {
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, "whatever");
assertThat(attributeDescriptor.read(null, queryExecutionContext))
.isEqualTo(QueryService.UNDEFINED);
assertThat(attributeDescriptor.read(QueryService.UNDEFINED, queryExecutionContext))
.isEqualTo(QueryService.UNDEFINED);
}
@Test
@Parameters({PUBLIC_NO_ACCESSORS, PUBLIC_ACCESSOR_BY_NAME, PUBLIC_ACCESSOR_BY_GETTER,
PRIVATE_ACCESSOR_BY_NAME, PRIVATE_ACCESSOR_BY_GETTER})
public void readShouldReturnCorrectlyForAccessibleAuthorizedNonPdxMembers(String attributeName)
throws NameNotFoundException, QueryInvocationTargetException {
doReturn(true).when(methodInvocationAuthorizer).authorize(any(), any());
AttributeDescriptor attributeDescriptor = new AttributeDescriptor(typeRegistry, attributeName);
Object result = attributeDescriptor.read(testBean, queryExecutionContext);
assertThat(result).isInstanceOf(String.class);
assertThat(result).isEqualTo(attributeName);
}
@SuppressWarnings("unused")
private static class TestBean {
public final String publicAttributeWithoutAccessors;
public final String publicAttributeWithPublicAccessor;
public final String publicAttributeWithPublicGetterMethod;
private final String nonPublicAttributeWithPublicAccessor;
private final String nonPublicAttributeWithPublicGetterMethod;
public String publicAttributeWithPublicAccessor() {
return publicAttributeWithPublicAccessor;
}
public String getPublicAttributeWithPublicGetterMethod() {
return publicAttributeWithPublicGetterMethod;
}
public String nonPublicAttributeWithPublicAccessor() {
return nonPublicAttributeWithPublicAccessor;
}
public String getNonPublicAttributeWithPublicGetterMethod() {
return nonPublicAttributeWithPublicGetterMethod;
}
public void throwEntryDestroyedExceptionMethod() {
throw new EntryDestroyedException();
}
TestBean(String publicAttributeWithoutAccessors,
String publicAttributeWithPublicAccessor,
String publicAttributeWithPublicGetterMethod,
String nonPublicAttributeWithPublicAccessor,
String nonPublicAttributeWithPublicGetterMethod) {
this.publicAttributeWithoutAccessors = publicAttributeWithoutAccessors;
this.publicAttributeWithPublicAccessor = publicAttributeWithPublicAccessor;
this.publicAttributeWithPublicGetterMethod = publicAttributeWithPublicGetterMethod;
this.nonPublicAttributeWithPublicAccessor = nonPublicAttributeWithPublicAccessor;
this.nonPublicAttributeWithPublicGetterMethod = nonPublicAttributeWithPublicGetterMethod;
}
}
}