| /* |
| * 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() {} |
| } |
| |
| } |