/* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import util.ClassSupplier; import util.MemberFactory; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.EnumSet; import java.util.Iterator; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.mapping; import static java.util.stream.Collectors.toCollection; import static util.MemberFactory.*; import static util.MemberFactory.Group.*; import static util.ClassSupplier.*; /** * @test * @summary An exhaustive test of reflective access controls * @bug 6378384 * @build a.PublicSuper a.Package b.PublicSub b.Package * util.MemberFactory util.ClassSupplier * @run main AccessControlTest */ public class AccessControlTest { public static void main(String[] args) throws Exception { boolean ok = true; ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_A) .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A) .allowed(ALL) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_A) .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A) .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_A) .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B) .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_A) .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B) .denied (ALL) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_A) .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B) .allowed(PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS) .perform(); ok &= new Test() .current(PUBLIC_SUPERCLASS_IN_PKG_A) .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A) .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS) .perform(); ok &= new Test() .current(PUBLIC_SUPERCLASS_IN_PKG_A) .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A) .allowed(ALL) .perform(); ok &= new Test() .current(PUBLIC_SUPERCLASS_IN_PKG_A) .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B) .allowed(ALL) .perform(); ok &= new Test() .current(PUBLIC_SUPERCLASS_IN_PKG_A) .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B) .denied (ALL) .perform(); ok &= new Test() .current(PUBLIC_SUPERCLASS_IN_PKG_A) .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B) .allowed(PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_B) .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A) .denied (ALL) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_B) .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A) .allowed(PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_B) .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B) .allowed(PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_B) .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B) .allowed(ALL) .perform(); ok &= new Test() .current(PACKAGE_CLASS_IN_PKG_B) .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B) .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS) .perform(); ok &= new Test() .current(PUBLIC_SUBCLASS_IN_PKG_B) .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A) .denied (ALL) .perform(); ok &= new Test() .current(PUBLIC_SUBCLASS_IN_PKG_B) .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A) .allowed(PUBLIC_MEMBERS, PROTECTED_STATIC_F_M) .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_INSTANCE_F_M, PROTECTED_C) .perform(); ok &= new Test() .current(PUBLIC_SUBCLASS_IN_PKG_B) .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B) .allowed(PUBLIC_MEMBERS, PROTECTED_INSTANCE_F_M, PROTECTED_STATIC_F_M) .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_C) .perform(); ok &= new Test() .current(PUBLIC_SUBCLASS_IN_PKG_B) .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B) .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS) .denied (PRIVATE_MEMBERS) .perform(); ok &= new Test() .current(PUBLIC_SUBCLASS_IN_PKG_B) .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B) .allowed(ALL) .perform(); if (ok) { System.out.println("\nAll cases passed."); } else { throw new RuntimeException("Some cases failed - see log."); } } // use this for generating an exhaustive set of test cases on stdout public static class Generate { public static void main(String[] args) { for (ClassSupplier current : ClassSupplier.values()) { for (ClassSupplier member : ClassSupplier.values()) { for (ClassSupplier target : ClassSupplier.values()) { if (member.get().isAssignableFrom(target.get())) { new Test() .current(current).member(member).target(target) .allowed(ALL) .perform(true); } } } } } } static class Test { ClassSupplier currentClassSupplier, memberClassSupplier, targetClassSupplier; EnumSet<MemberFactory> expectAllowedMembers = EnumSet.noneOf(MemberFactory.class); EnumSet<MemberFactory> expectDeniedMembers = EnumSet.noneOf(MemberFactory.class); Test current(ClassSupplier current) { currentClassSupplier = current; return this; } Test member(ClassSupplier member) { memberClassSupplier = member; return this; } Test target(ClassSupplier target) { targetClassSupplier = target; return this; } Test allowed(MemberFactory... allowed) { expectAllowedMembers = MemberFactory.asSet(allowed); return this; } Test allowed(MemberFactory.Group... allowedGroups) { expectAllowedMembers = MemberFactory.groupsToMembers( MemberFactory.Group.asSet(allowedGroups)); return this; } Test denied(MemberFactory... denied) { expectDeniedMembers = MemberFactory.asSet(denied); return this; } Test denied(MemberFactory.Group... deniedGroups) { expectDeniedMembers = MemberFactory.groupsToMembers( MemberFactory.Group.asSet(deniedGroups)); return this; } boolean perform() { return perform(false); } boolean perform(boolean generateCases) { // some validation 1st EnumSet<MemberFactory> intersection = EnumSet.copyOf(expectAllowedMembers); intersection.retainAll(expectDeniedMembers); if (!intersection.isEmpty()) { throw new IllegalArgumentException( "Expected allowed and denied MemberFactories have non-empty intersection: " + intersection); } EnumSet<MemberFactory> missing = EnumSet.allOf(MemberFactory.class); missing.removeAll(expectAllowedMembers); missing.removeAll(expectDeniedMembers); if (!missing.isEmpty()) { throw new IllegalArgumentException( "Union of expected allowed and denied MemberFactories is missing elements: " + missing); } // retrieve method that will perform reflective access Method checkAccessMethod; try { checkAccessMethod = currentClassSupplier.get().getDeclaredMethod( "checkAccess", AccessibleObject.class, Object.class); // in case of inaccessible currentClass checkAccessMethod.setAccessible(true); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } // construct a target object (for instance field/method) Object target; Constructor<?> targetConstructor = (Constructor<?>) PUBLIC_CONSTRUCTOR.apply(targetClassSupplier.get()); // in case of inaccessible targetClass targetConstructor.setAccessible(true); try { target = targetConstructor.newInstance( new Object[targetConstructor.getParameterCount()]); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } Class<?> memberClass = memberClassSupplier.get(); Map<Boolean, EnumSet<MemberFactory>> actualMembers = Stream.concat( expectAllowedMembers.stream().map(member -> new Trial(member, true)), expectDeniedMembers.stream().map(member -> new Trial(member, false)) ).map(trial -> { // obtain AccessibleObject to be used to perform reflective access AccessibleObject accessibleObject = trial.member.apply(memberClass); // only need target 'obj' for instance fields and methods Object obj = (accessibleObject instanceof Field && !Modifier.isStatic(((Field) accessibleObject).getModifiers()) || accessibleObject instanceof Method && !Modifier.isStatic(((Method) accessibleObject).getModifiers()) ) ? target : null; // invoke checkAccess method and let it perform the reflective access try { checkAccessMethod.invoke(null, accessibleObject, obj); trial.actualAllowed = true; } catch (IllegalAccessException e) { // should not happen as checkAccessMethod.isAccessible() throw new RuntimeException(e); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof IllegalAccessException) { trial.actualAllowed = false; } else { // any other Exception is a fault in test or infrastructure - fail fast throw new RuntimeException(e.getTargetException()); } } if (!generateCases) { System.out.printf( "%-26s accessing %26s's %-25s %-43s - expected %s, actual %s: %s\n", currentClassSupplier, memberClassSupplier, trial.member.name(), (obj == null ? "" : "with instance of " + targetClassSupplier), (trial.expectAllowed ? "allowed" : "denied "), (trial.actualAllowed ? "allowed" : "denied "), (trial.expectAllowed == trial.actualAllowed ? "OK" : "FAILURE") ); } return trial; }).collect( groupingBy( Trial::isActualAllowed, mapping( Trial::getMember, toCollection(() -> EnumSet.noneOf(MemberFactory.class)))) ); EnumSet<MemberFactory> actualAllowedMembers = Optional.ofNullable(actualMembers.get(true)) .orElse(EnumSet.noneOf(MemberFactory.class)); EnumSet<MemberFactory> actualDeniedMembers = Optional.ofNullable(actualMembers.get(false)) .orElse(EnumSet.noneOf(MemberFactory.class)); if (generateCases) { System.out.printf( " ok &= new Test()\n" + " .current(%s)\n" + " .member (%s).target(%s)\n", currentClassSupplier, memberClassSupplier, targetClassSupplier ); if (!actualAllowedMembers.isEmpty()) { EnumSet<? extends Enum> actualAllowed = MemberFactory.membersToGroupsOrNull(actualAllowedMembers); if (actualAllowed == null) actualAllowed = actualAllowedMembers; System.out.print( chunkBy(3, actualAllowed.stream().map(Enum::name)) .map(chunk -> chunk.collect(joining(", "))) .collect(joining(",\n" + " ", " .allowed(", ")\n")) ); } if (!actualDeniedMembers.isEmpty()) { EnumSet<? extends Enum> actualDenied = MemberFactory.membersToGroupsOrNull(actualDeniedMembers); if (actualDenied == null) actualDenied = actualAllowedMembers; System.out.print( chunkBy(3, actualDenied.stream().map(Enum::name)) .map(chunk -> chunk.collect(joining(", "))) .collect(joining(",\n" + " ", " .denied (", ")\n")) ); } System.out.print( " .perform();\n" ); } return expectAllowedMembers.equals(actualAllowedMembers) && expectDeniedMembers.equals(actualDeniedMembers); } } private static <T> Stream<Stream<T>> chunkBy(int chunkSize, Stream<T> stream) { Iterator<T> elements = stream.iterator(); Stream.Builder<Stream<T>> b1 = Stream.builder(); while (elements.hasNext()) { Stream.Builder<T> b2 = Stream.builder(); for (int i = 0; i < chunkSize && elements.hasNext(); i++) { b2.accept(elements.next()); } b1.accept(b2.build()); } return b1.build(); } private static class Trial { final MemberFactory member; final boolean expectAllowed; boolean actualAllowed; Trial(MemberFactory member, boolean expectAllowed) { this.member = member; this.expectAllowed = expectAllowed; } MemberFactory getMember() { return member; } boolean isActualAllowed() { return actualAllowed; } } }