| /* |
| * 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; |
| } |
| } |
| } |