1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import util.ClassSupplier;
  25 import util.MemberFactory;
  26 
  27 import java.lang.reflect.AccessibleObject;
  28 import java.lang.reflect.Constructor;
  29 import java.lang.reflect.Field;
  30 import java.lang.reflect.InvocationTargetException;
  31 import java.lang.reflect.Method;
  32 import java.lang.reflect.Modifier;
  33 import java.util.EnumSet;
  34 import java.util.Iterator;
  35 import java.util.Map;
  36 import java.util.Optional;
  37 import java.util.stream.Stream;
  38 
  39 import static java.util.stream.Collectors.groupingBy;
  40 import static java.util.stream.Collectors.joining;
  41 import static java.util.stream.Collectors.mapping;
  42 import static java.util.stream.Collectors.toCollection;
  43 import static util.MemberFactory.*;
  44 import static util.MemberFactory.Group.*;
  45 import static util.ClassSupplier.*;
  46 
  47 /**
  48  * @test
  49  * @summary An exhaustive test of reflective access controls
  50  * @bug 6378384
  51  * @build a.PublicSuper a.Package b.PublicSub b.Package
  52  *        util.MemberFactory util.ClassSupplier
  53  * @run main AccessControlTest
  54  */
  55 public class AccessControlTest {
  56 
  57     public static void main(String[] args) throws Exception {
  58         boolean ok = true;
  59 
  60         ok &= new Test()
  61             .current(PACKAGE_CLASS_IN_PKG_A)
  62             .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A)
  63             .allowed(ALL)
  64             .perform();
  65 
  66         ok &= new Test()
  67             .current(PACKAGE_CLASS_IN_PKG_A)
  68             .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A)
  69             .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS)
  70             .denied (PRIVATE_MEMBERS)
  71             .perform();
  72 
  73         ok &= new Test()
  74             .current(PACKAGE_CLASS_IN_PKG_A)
  75             .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B)
  76             .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS)
  77             .denied (PRIVATE_MEMBERS)
  78             .perform();
  79 
  80         ok &= new Test()
  81             .current(PACKAGE_CLASS_IN_PKG_A)
  82             .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B)
  83             .denied (ALL)
  84             .perform();
  85 
  86         ok &= new Test()
  87             .current(PACKAGE_CLASS_IN_PKG_A)
  88             .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B)
  89             .allowed(PUBLIC_MEMBERS)
  90             .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS)
  91             .perform();
  92 
  93         ok &= new Test()
  94             .current(PUBLIC_SUPERCLASS_IN_PKG_A)
  95             .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A)
  96             .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS)
  97             .denied (PRIVATE_MEMBERS)
  98             .perform();
  99 
 100         ok &= new Test()
 101             .current(PUBLIC_SUPERCLASS_IN_PKG_A)
 102             .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A)
 103             .allowed(ALL)
 104             .perform();
 105 
 106         ok &= new Test()
 107             .current(PUBLIC_SUPERCLASS_IN_PKG_A)
 108             .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B)
 109             .allowed(ALL)
 110             .perform();
 111 
 112         ok &= new Test()
 113             .current(PUBLIC_SUPERCLASS_IN_PKG_A)
 114             .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B)
 115             .denied (ALL)
 116             .perform();
 117 
 118         ok &= new Test()
 119             .current(PUBLIC_SUPERCLASS_IN_PKG_A)
 120             .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B)
 121             .allowed(PUBLIC_MEMBERS)
 122             .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS)
 123             .perform();
 124 
 125         ok &= new Test()
 126             .current(PACKAGE_CLASS_IN_PKG_B)
 127             .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A)
 128             .denied (ALL)
 129             .perform();
 130 
 131         ok &= new Test()
 132             .current(PACKAGE_CLASS_IN_PKG_B)
 133             .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A)
 134             .allowed(PUBLIC_MEMBERS)
 135             .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS)
 136             .perform();
 137 
 138         ok &= new Test()
 139             .current(PACKAGE_CLASS_IN_PKG_B)
 140             .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B)
 141             .allowed(PUBLIC_MEMBERS)
 142             .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_MEMBERS)
 143             .perform();
 144 
 145         ok &= new Test()
 146             .current(PACKAGE_CLASS_IN_PKG_B)
 147             .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B)
 148             .allowed(ALL)
 149             .perform();
 150 
 151         ok &= new Test()
 152             .current(PACKAGE_CLASS_IN_PKG_B)
 153             .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B)
 154             .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS)
 155             .denied (PRIVATE_MEMBERS)
 156             .perform();
 157 
 158         ok &= new Test()
 159             .current(PUBLIC_SUBCLASS_IN_PKG_B)
 160             .member (PACKAGE_CLASS_IN_PKG_A).target(PACKAGE_CLASS_IN_PKG_A)
 161             .denied (ALL)
 162             .perform();
 163 
 164         ok &= new Test()
 165             .current(PUBLIC_SUBCLASS_IN_PKG_B)
 166             .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUPERCLASS_IN_PKG_A)
 167             .allowed(PUBLIC_MEMBERS, PROTECTED_STATIC_F_M)
 168             .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_INSTANCE_F_M,
 169                      PROTECTED_C)
 170             .perform();
 171 
 172         ok &= new Test()
 173             .current(PUBLIC_SUBCLASS_IN_PKG_B)
 174             .member (PUBLIC_SUPERCLASS_IN_PKG_A).target(PUBLIC_SUBCLASS_IN_PKG_B)
 175             .allowed(PUBLIC_MEMBERS, PROTECTED_INSTANCE_F_M, PROTECTED_STATIC_F_M)
 176             .denied (PRIVATE_MEMBERS, PACKAGE_MEMBERS, PROTECTED_C)
 177             .perform();
 178 
 179         ok &= new Test()
 180             .current(PUBLIC_SUBCLASS_IN_PKG_B)
 181             .member (PACKAGE_CLASS_IN_PKG_B).target(PACKAGE_CLASS_IN_PKG_B)
 182             .allowed(PACKAGE_MEMBERS, PROTECTED_MEMBERS, PUBLIC_MEMBERS)
 183             .denied (PRIVATE_MEMBERS)
 184             .perform();
 185 
 186         ok &= new Test()
 187             .current(PUBLIC_SUBCLASS_IN_PKG_B)
 188             .member (PUBLIC_SUBCLASS_IN_PKG_B).target(PUBLIC_SUBCLASS_IN_PKG_B)
 189             .allowed(ALL)
 190             .perform();
 191 
 192         if (ok) {
 193             System.out.println("\nAll cases passed.");
 194         } else {
 195             throw new RuntimeException("Some cases failed - see log.");
 196         }
 197     }
 198 
 199     // use this for generating an exhaustive set of test cases on stdout
 200     public static class Generate {
 201         public static void main(String[] args) {
 202             for (ClassSupplier current : ClassSupplier.values()) {
 203                 for (ClassSupplier member : ClassSupplier.values()) {
 204                     for (ClassSupplier target : ClassSupplier.values()) {
 205                         if (member.get().isAssignableFrom(target.get())) {
 206                             new Test()
 207                                 .current(current).member(member).target(target)
 208                                 .allowed(ALL)
 209                                 .perform(true);
 210                         }
 211                     }
 212                 }
 213             }
 214         }
 215     }
 216 
 217     static class Test {
 218 
 219         ClassSupplier currentClassSupplier, memberClassSupplier, targetClassSupplier;
 220         EnumSet<MemberFactory> expectAllowedMembers = EnumSet.noneOf(MemberFactory.class);
 221         EnumSet<MemberFactory> expectDeniedMembers = EnumSet.noneOf(MemberFactory.class);
 222 
 223         Test current(ClassSupplier current) {
 224             currentClassSupplier = current;
 225             return this;
 226         }
 227 
 228         Test member(ClassSupplier member) {
 229             memberClassSupplier = member;
 230             return this;
 231         }
 232 
 233         Test target(ClassSupplier target) {
 234             targetClassSupplier = target;
 235             return this;
 236         }
 237 
 238         Test allowed(MemberFactory... allowed) {
 239             expectAllowedMembers = MemberFactory.asSet(allowed);
 240             return this;
 241         }
 242 
 243         Test allowed(MemberFactory.Group... allowedGroups) {
 244             expectAllowedMembers = MemberFactory.groupsToMembers(
 245                 MemberFactory.Group.asSet(allowedGroups));
 246             return this;
 247         }
 248 
 249         Test denied(MemberFactory... denied) {
 250             expectDeniedMembers = MemberFactory.asSet(denied);
 251             return this;
 252         }
 253 
 254         Test denied(MemberFactory.Group... deniedGroups) {
 255             expectDeniedMembers = MemberFactory.groupsToMembers(
 256                 MemberFactory.Group.asSet(deniedGroups));
 257             return this;
 258         }
 259 
 260         boolean perform() {
 261             return perform(false);
 262         }
 263 
 264         boolean perform(boolean generateCases) {
 265 
 266             // some validation 1st
 267             EnumSet<MemberFactory> intersection = EnumSet.copyOf(expectAllowedMembers);
 268             intersection.retainAll(expectDeniedMembers);
 269             if (!intersection.isEmpty()) {
 270                 throw new IllegalArgumentException(
 271                     "Expected allowed and denied MemberFactories have non-empty intersection: " +
 272                     intersection);
 273             }
 274 
 275             EnumSet<MemberFactory> missing = EnumSet.allOf(MemberFactory.class);
 276             missing.removeAll(expectAllowedMembers);
 277             missing.removeAll(expectDeniedMembers);
 278             if (!missing.isEmpty()) {
 279                 throw new IllegalArgumentException(
 280                     "Union of expected allowed and denied MemberFactories is missing elements: " +
 281                     missing);
 282             }
 283 
 284             // retrieve method that will perform reflective access
 285             Method checkAccessMethod;
 286             try {
 287                 checkAccessMethod = currentClassSupplier.get().getDeclaredMethod(
 288                     "checkAccess", AccessibleObject.class, Object.class);
 289                 // in case of inaccessible currentClass
 290                 checkAccessMethod.setAccessible(true);
 291             } catch (NoSuchMethodException e) {
 292                 throw new RuntimeException(e);
 293             }
 294 
 295             // construct a target object (for instance field/method)
 296             Object target;
 297             Constructor<?> targetConstructor =
 298                 (Constructor<?>) PUBLIC_CONSTRUCTOR.apply(targetClassSupplier.get());
 299             // in case of inaccessible targetClass
 300             targetConstructor.setAccessible(true);
 301             try {
 302                 target = targetConstructor.newInstance(
 303                     new Object[targetConstructor.getParameterCount()]);
 304             } catch (ReflectiveOperationException e) {
 305                 throw new RuntimeException(e);
 306             }
 307 
 308             Class<?> memberClass = memberClassSupplier.get();
 309 
 310             Map<Boolean, EnumSet<MemberFactory>> actualMembers = Stream.concat(
 311 
 312                 expectAllowedMembers.stream().map(member -> new Trial(member, true)),
 313                 expectDeniedMembers.stream().map(member -> new Trial(member, false))
 314 
 315             ).map(trial -> {
 316 
 317                 // obtain AccessibleObject to be used to perform reflective access
 318                 AccessibleObject accessibleObject = trial.member.apply(memberClass);
 319 
 320                 // only need target 'obj' for instance fields and methods
 321                 Object obj =
 322                     (accessibleObject instanceof Field &&
 323                      !Modifier.isStatic(((Field) accessibleObject).getModifiers())
 324                      ||
 325                      accessibleObject instanceof Method &&
 326                      !Modifier.isStatic(((Method) accessibleObject).getModifiers())
 327                     )
 328                     ? target : null;
 329 
 330                 // invoke checkAccess method and let it perform the reflective access
 331                 try {
 332                     checkAccessMethod.invoke(null, accessibleObject, obj);
 333                     trial.actualAllowed = true;
 334                 } catch (IllegalAccessException e) {
 335                     // should not happen as checkAccessMethod.isAccessible()
 336                     throw new RuntimeException(e);
 337                 } catch (InvocationTargetException e) {
 338                     if (e.getTargetException() instanceof IllegalAccessException) {
 339                         trial.actualAllowed = false;
 340                     } else {
 341                         // any other Exception is a fault in test or infrastructure - fail fast
 342                         throw new RuntimeException(e.getTargetException());
 343                     }
 344                 }
 345 
 346                 if (!generateCases) {
 347                     System.out.printf(
 348                         "%-26s accessing %26s's %-25s %-43s - expected %s, actual %s: %s\n",
 349                         currentClassSupplier, memberClassSupplier, trial.member.name(),
 350                         (obj == null ? "" : "with instance of " + targetClassSupplier),
 351                         (trial.expectAllowed ? "allowed" : "denied "),
 352                         (trial.actualAllowed ? "allowed" : "denied "),
 353                         (trial.expectAllowed == trial.actualAllowed ? "OK" : "FAILURE")
 354                     );
 355                 }
 356 
 357                 return trial;
 358 
 359             }).collect(
 360                 groupingBy(
 361                     Trial::isActualAllowed,
 362                     mapping(
 363                         Trial::getMember,
 364                         toCollection(() -> EnumSet.noneOf(MemberFactory.class))))
 365             );
 366 
 367             EnumSet<MemberFactory> actualAllowedMembers =
 368                 Optional.ofNullable(actualMembers.get(true))
 369                         .orElse(EnumSet.noneOf(MemberFactory.class));
 370             EnumSet<MemberFactory> actualDeniedMembers =
 371                 Optional.ofNullable(actualMembers.get(false))
 372                         .orElse(EnumSet.noneOf(MemberFactory.class));
 373 
 374             if (generateCases) {
 375                 System.out.printf(
 376                     "        ok &= new Test()\n" +
 377                     "            .current(%s)\n" +
 378                     "            .member (%s).target(%s)\n",
 379                     currentClassSupplier,
 380                     memberClassSupplier, targetClassSupplier
 381                 );
 382 
 383                 if (!actualAllowedMembers.isEmpty()) {
 384                     EnumSet<? extends Enum> actualAllowed =
 385                         MemberFactory.membersToGroupsOrNull(actualAllowedMembers);
 386                     if (actualAllowed == null)
 387                         actualAllowed = actualAllowedMembers;
 388                     System.out.print(
 389                         chunkBy(3, actualAllowed.stream().map(Enum::name))
 390                             .map(chunk -> chunk.collect(joining(", ")))
 391                             .collect(joining(",\n" +
 392                                              "                     ",
 393                                              "            .allowed(",
 394                                              ")\n"))
 395                     );
 396                 }
 397 
 398                 if (!actualDeniedMembers.isEmpty()) {
 399                     EnumSet<? extends Enum> actualDenied =
 400                         MemberFactory.membersToGroupsOrNull(actualDeniedMembers);
 401                     if (actualDenied == null)
 402                         actualDenied = actualAllowedMembers;
 403                     System.out.print(
 404                         chunkBy(3, actualDenied.stream().map(Enum::name))
 405                             .map(chunk -> chunk.collect(joining(", ")))
 406                             .collect(joining(",\n" +
 407                                              "                     ",
 408                                              "            .denied (",
 409                                              ")\n"))
 410                     );
 411                 }
 412 
 413                 System.out.print(
 414                     "            .perform();\n"
 415                 );
 416             }
 417 
 418             return expectAllowedMembers.equals(actualAllowedMembers) &&
 419                    expectDeniedMembers.equals(actualDeniedMembers);
 420         }
 421     }
 422 
 423     private static <T> Stream<Stream<T>> chunkBy(int chunkSize, Stream<T> stream) {
 424         Iterator<T> elements = stream.iterator();
 425         Stream.Builder<Stream<T>> b1 = Stream.builder();
 426         while (elements.hasNext()) {
 427             Stream.Builder<T> b2 = Stream.builder();
 428             for (int i = 0; i < chunkSize && elements.hasNext(); i++) {
 429                 b2.accept(elements.next());
 430             }
 431             b1.accept(b2.build());
 432         }
 433         return b1.build();
 434     }
 435 
 436     private static class Trial {
 437         final MemberFactory member;
 438         final boolean expectAllowed;
 439         boolean actualAllowed;
 440 
 441         Trial(MemberFactory member, boolean expectAllowed) {
 442             this.member = member;
 443             this.expectAllowed = expectAllowed;
 444         }
 445 
 446         MemberFactory getMember() {
 447             return member;
 448         }
 449 
 450         boolean isActualAllowed() {
 451             return actualAllowed;
 452         }
 453     }
 454 }