blob: 4870743852ffd77623eb75d74f23b7ec66629f50 [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.security;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.apache.geode.cache.Cache;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.test.junit.categories.SecurityTest;
@Category(SecurityTest.class)
public class JavaBeanAccessorMethodAuthorizerTest {
private InternalCache mockCache;
private JavaBeanAccessorMethodAuthorizer authorizerWithLangAndIOPackagesSpecified;
private RestrictedMethodAuthorizer defaultAuthorizer;
private final String LANG_PACKAGE = String.class.getPackage().getName();
private final String IO_PACKAGE = File.class.getPackage().getName();
@Before
public void setUp() {
mockCache = mock(InternalCache.class);
defaultAuthorizer = new RestrictedMethodAuthorizer(mockCache);
Set<String> allowedPackages = new HashSet<>();
allowedPackages.add(LANG_PACKAGE);
allowedPackages.add(IO_PACKAGE);
authorizerWithLangAndIOPackagesSpecified =
new JavaBeanAccessorMethodAuthorizer(defaultAuthorizer, allowedPackages);
}
@Test
public void constructorThrowsExceptionWhenCacheIsNull() {
assertThatThrownBy(() -> new JavaBeanAccessorMethodAuthorizer((Cache) null, new HashSet<>()))
.isInstanceOf(NullPointerException.class)
.hasMessage(JavaBeanAccessorMethodAuthorizer.NULL_CACHE_MESSAGE);
}
@Test
public void constructorThrowsExceptionWhenRestrictedMethodAuthorizerIsNull() {
assertThatThrownBy(() -> new JavaBeanAccessorMethodAuthorizer((RestrictedMethodAuthorizer) null,
new HashSet<>()))
.isInstanceOf(NullPointerException.class)
.hasMessage(JavaBeanAccessorMethodAuthorizer.NULL_AUTHORIZER_MESSAGE);
}
@Test
public void constructorsThrowsExceptionWhenAllowedPackagesIsNull() {
assertThatThrownBy(() -> new JavaBeanAccessorMethodAuthorizer(mockCache, null))
.isInstanceOf(NullPointerException.class)
.hasMessage(JavaBeanAccessorMethodAuthorizer.NULL_PACKAGE_MESSAGE);
assertThatThrownBy(
() -> new JavaBeanAccessorMethodAuthorizer(defaultAuthorizer, null))
.isInstanceOf(NullPointerException.class)
.hasMessage(JavaBeanAccessorMethodAuthorizer.NULL_PACKAGE_MESSAGE);
}
@Test
public void authorizeReturnsFalseForKnownDangerousMethods() throws NoSuchMethodException {
List<Method> dangerousMethods = new ArrayList<>();
dangerousMethods.add(TestBean.class.getMethod("getClass"));
dangerousMethods.add(TestBean.class.getMethod("readResolve"));
dangerousMethods.add(TestBean.class.getMethod("readObjectNoData"));
dangerousMethods.add(TestBean.class.getMethod("readObject", ObjectInputStream.class));
dangerousMethods.add(TestBean.class.getMethod("writeReplace"));
dangerousMethods.add(TestBean.class.getMethod("writeObject", ObjectOutputStream.class));
dangerousMethods.forEach(
method -> assertThat(
authorizerWithLangAndIOPackagesSpecified.authorize(method, new TestBean()))
.isFalse());
}
@Test
public void authorizeReturnsFalseForDisallowedGeodeClassesWithGeodePackageSpecified()
throws NoSuchMethodException {
assertThat((TestBean.class.getPackage().getName()))
.startsWith(JavaBeanAccessorMethodAuthorizer.GEODE_BASE_PACKAGE);
List<Method> geodeMethods = new ArrayList<>();
geodeMethods.add(TestBean.class.getMethod("isMatchingMethod"));
geodeMethods.add(TestBean.class.getMethod("getMatchingMethod"));
geodeMethods.add(TestBean.class.getMethod("nonMatchingMethod"));
Set<String> geodePackage = new HashSet<>();
geodePackage.add(JavaBeanAccessorMethodAuthorizer.GEODE_BASE_PACKAGE);
JavaBeanAccessorMethodAuthorizer geodeMatchingAuthorizer =
new JavaBeanAccessorMethodAuthorizer(defaultAuthorizer, geodePackage);
geodeMethods.forEach(
method -> assertThat(geodeMatchingAuthorizer.authorize(method, new TestBean())).isFalse());
}
@Test
public void authorizeReturnsFalseForMatchingMethodNamesAndNonMatchingPackage()
throws NoSuchMethodException {
Method getMatchingMethod = List.class.getMethod("get", int.class);
Method isMatchingMethod = List.class.getMethod("isEmpty");
assertThat(
authorizerWithLangAndIOPackagesSpecified.authorize(isMatchingMethod, new ArrayList()))
.isFalse();
assertThat(
authorizerWithLangAndIOPackagesSpecified.authorize(getMatchingMethod, new ArrayList()))
.isFalse();
}
@Test
public void authorizeReturnsFalseForNonMatchingMethodNameAndMatchingPackage()
throws NoSuchMethodException {
Method langMethod = String.class.getMethod("notify");
assertThat(authorizerWithLangAndIOPackagesSpecified.authorize(langMethod, "")).isFalse();
Method ioMethod = File.class.getMethod("notify");
assertThat(authorizerWithLangAndIOPackagesSpecified.authorize(ioMethod, new File("")))
.isFalse();
}
@Test
public void authorizeReturnsTrueForMatchingMethodNamesAndPackage() throws NoSuchMethodException {
Method isMatchingLangMethod = String.class.getMethod("isEmpty");
Method getMatchingLangMethod = String.class.getMethod("getBytes");
assertThat(authorizerWithLangAndIOPackagesSpecified.authorize(isMatchingLangMethod, ""))
.isTrue();
assertThat(authorizerWithLangAndIOPackagesSpecified.authorize(getMatchingLangMethod, ""))
.isTrue();
Method isMatchingIOMethod = File.class.getMethod("isAbsolute");
Method getMatchingIOMethod = File.class.getMethod("getPath");
assertThat(authorizerWithLangAndIOPackagesSpecified.authorize(isMatchingIOMethod, new File("")))
.isTrue();
assertThat(
authorizerWithLangAndIOPackagesSpecified.authorize(getMatchingIOMethod, new File("")))
.isTrue();
}
@Test
public void authorizeReturnsFalseForNonMatchingDisallowedMethod() throws NoSuchMethodException {
Method method = Object.class.getMethod("notify");
assertThat(authorizerWithLangAndIOPackagesSpecified.authorize(method, new Object())).isFalse();
}
@Test
public void authorizeReturnsTrueForNonMatchingAllowedMethod() throws NoSuchMethodException {
Method method = Object.class.getMethod("equals", Object.class);
assertThat(authorizerWithLangAndIOPackagesSpecified.authorize(method, new Object())).isTrue();
}
@Test
public void allowedPackagesIsUnmodifiable() {
assertThatThrownBy(
() -> authorizerWithLangAndIOPackagesSpecified.getAllowedPackages().remove(LANG_PACKAGE))
.isInstanceOf(UnsupportedOperationException.class);
}
@SuppressWarnings("unused")
private static class TestBean implements Serializable {
public Object writeReplace() throws ObjectStreamException {
return new TestBean();
}
public void writeObject(ObjectOutputStream stream) throws IOException {
throw new IOException();
}
public Object readResolve() throws ObjectStreamException {
return new TestBean();
}
public void readObjectNoData() throws ObjectStreamException {}
public void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
if (new Random().nextBoolean()) {
throw new IOException();
} else {
throw new ClassNotFoundException();
}
}
public void isMatchingMethod() {}
public void getMatchingMethod() {}
public void nonMatchingMethod() {}
}
}