1 /*
2  * Copyright (c) 2014, 2019, 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 import java.io.ByteArrayInputStream;
24 import java.io.FilePermission;
25 import java.io.IOException;
26 import java.security.AccessControlException;
27 import java.security.CodeSource;
28 import java.security.Permission;
29 import java.security.PermissionCollection;
30 import java.security.Permissions;
31 import java.security.Policy;
32 import java.security.ProtectionDomain;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.ConcurrentModificationException;
36 import java.util.Enumeration;
37 import java.util.HashSet;
38 import java.util.PropertyPermission;
39 import java.util.Set;
40 import java.util.concurrent.atomic.AtomicLong;
41 import java.util.logging.LogManager;
42 import java.util.logging.LoggingPermission;
43 
44 /**
45  * @test
46  * @bug 8043306
47  * @summary tests LogManager.addConfigurationListener and
48  *                LogManager.removeConfigurationListener;
49  * @build TestConfigurationListeners
50  * @run main/othervm TestConfigurationListeners UNSECURE
51  * @run main/othervm TestConfigurationListeners PERMISSION
52  * @run main/othervm TestConfigurationListeners SECURE
53  * @author danielfuchs
54  */
55 public class TestConfigurationListeners {
56 
57     /**
58      * We will test add and remove ConfigurationListeners in 3 configurations.
59      * UNSECURE: No security manager.
60      * SECURE: With the security manager present - and the required
61      *         LoggingPermission("control") granted.
62      * PERMISSION: With the security manager present - and the required
63      *         LoggingPermission("control") *not* granted. Here we will
64      *         test that the expected security permission is thrown.
65      */
66     public static enum TestCase {
67         UNSECURE, SECURE, PERMISSION;
run(String name)68         public void run(String name) throws Exception {
69             System.out.println("Running test case: " + name());
70             switch (this) {
71                 case UNSECURE:
72                     testUnsecure(name);
73                     break;
74                 case SECURE:
75                     testSecure(name);
76                     break;
77                 case PERMISSION:
78                     testPermission(name);
79                     break;
80                 default:
81                     throw new Error("Unknown test case: "+this);
82             }
83         }
loggerName(String name)84         public String loggerName(String name) {
85             return name;
86         }
87     }
88 
main(String... args)89     public static void main(String... args) throws Exception {
90 
91 
92         if (args == null || args.length == 0) {
93             args = new String[] {
94                 TestCase.UNSECURE.name(),
95                 TestCase.SECURE.name(),
96             };
97         }
98 
99         for (String testName : args) {
100             TestCase test = TestCase.valueOf(testName);
101             test.run(test.loggerName("foo.bar"));
102         }
103     }
104 
105     /**
106      * Test without security manager.
107      * @param loggerName The logger to use.
108      * @throws Exception if the test fails.
109      */
testUnsecure(String loggerName)110     public static void testUnsecure(String loggerName) throws Exception {
111         if (System.getSecurityManager() != null) {
112             throw new Error("Security manager is set");
113         }
114         test(loggerName);
115     }
116 
117     /**
118      * Test with security manager.
119      * @param loggerName The logger to use.
120      * @throws Exception if the test fails.
121      */
testSecure(String loggerName)122     public static void testSecure(String loggerName) throws Exception {
123         if (System.getSecurityManager() != null) {
124             throw new Error("Security manager is already set");
125         }
126         Policy.setPolicy(new SimplePolicy(TestCase.SECURE));
127         System.setSecurityManager(new SecurityManager());
128         test(loggerName);
129     }
130 
131     /**
132      * Test the LoggingPermission("control") is required.
133      * @param loggerName The logger to use.
134      */
testPermission(String loggerName)135     public static void testPermission(String loggerName) {
136         TestConfigurationListener run = new TestConfigurationListener(
137                 TestCase.PERMISSION.toString());
138         if (System.getSecurityManager() != null) {
139             throw new Error("Security manager is already set");
140         }
141         Policy.setPolicy(new SimplePolicy(TestCase.PERMISSION));
142         System.setSecurityManager(new SecurityManager());
143 
144         try {
145             LogManager.getLogManager().addConfigurationListener(run);
146             throw new RuntimeException("addConfigurationListener: Permission not checked!");
147         } catch (AccessControlException x) {
148             boolean ok = false;
149             if (x.getPermission() instanceof LoggingPermission) {
150                 if ("control".equals(x.getPermission().getName())) {
151                     System.out.println("addConfigurationListener: Got expected exception: " + x);
152                     ok = true;
153                 }
154             }
155             if (!ok) {
156                 throw new RuntimeException("addConfigurationListener: Unexpected exception: "+x, x);
157             }
158         }
159 
160         try {
161             LogManager.getLogManager().removeConfigurationListener(run);
162             throw new RuntimeException("removeConfigurationListener: Permission not checked!");
163         } catch (AccessControlException x) {
164             boolean ok = false;
165             if (x.getPermission() instanceof LoggingPermission) {
166                 if ("control".equals(x.getPermission().getName())) {
167                     System.out.println("removeConfigurationListener: Got expected exception: " + x);
168                     ok = true;
169                 }
170             }
171             if (!ok) {
172                 throw new RuntimeException("removeConfigurationListener: Unexpected exception: "+x, x);
173             }
174         }
175         try {
176             LogManager.getLogManager().addConfigurationListener(null);
177             throw new RuntimeException(
178                     "addConfigurationListener(null): Expected NPE not thrown.");
179         } catch (NullPointerException npe) {
180             System.out.println("Got expected NPE: "+npe);
181         }
182 
183         try {
184             LogManager.getLogManager().removeConfigurationListener(null);
185             throw new RuntimeException(
186                     "removeConfigurationListener(null): Expected NPE not thrown.");
187         } catch (NullPointerException npe) {
188             System.out.println("Got expected NPE: "+npe);
189         }
190 
191 
192     }
193 
194 
195     static class TestConfigurationListener implements Runnable {
196         final AtomicLong  count = new AtomicLong(0);
197         final String name;
TestConfigurationListener(String name)198         TestConfigurationListener(String name) {
199             this.name = name;
200         }
201         @Override
run()202         public void run() {
203             final long times = count.incrementAndGet();
204             System.out.println("Configured \"" + name + "\": " + times);
205         }
206     }
207 
208     static class ConfigurationListenerException extends RuntimeException {
ConfigurationListenerException(String msg)209         public ConfigurationListenerException(String msg) {
210             super(msg);
211         }
212 
213         @Override
toString()214         public String toString() {
215             return this.getClass().getName() + ": " + getMessage();
216         }
217     }
218     static class ConfigurationListenerError extends Error {
ConfigurationListenerError(String msg)219         public ConfigurationListenerError(String msg) {
220             super(msg);
221         }
222 
223         @Override
toString()224         public String toString() {
225             return this.getClass().getName() + ": " + getMessage();
226         }
227     }
228 
229     static class ThrowingConfigurationListener extends TestConfigurationListener {
230 
231         final boolean error;
ThrowingConfigurationListener(String name, boolean error)232         public ThrowingConfigurationListener(String name, boolean error) {
233             super(name);
234             this.error = error;
235         }
236 
237         @Override
run()238         public void run() {
239             if (error)
240                 throw new ConfigurationListenerError(name);
241             else
242                 throw new ConfigurationListenerException(name);
243         }
244 
245         @Override
toString()246         public String toString() {
247             final Class<? extends Throwable> type =
248                     error ? ConfigurationListenerError.class
249                           : ConfigurationListenerException.class;
250             return  type.getName()+ ": " + name;
251         }
252 
253     }
254 
expect(TestConfigurationListener listener, long value)255     private static void expect(TestConfigurationListener listener, long value) {
256         final long got = listener.count.longValue();
257         if (got != value) {
258             throw new RuntimeException(listener.name + " expected " + value +", got " + got);
259         }
260 
261     }
262 
263     public interface ThrowingConsumer<T, I extends Exception> {
accept(T t)264         public void accept(T t) throws I;
265     }
266 
267     public static class ReadConfiguration implements ThrowingConsumer<LogManager, IOException> {
268 
269         @Override
accept(LogManager t)270         public void accept(LogManager t) throws IOException {
271             t.readConfiguration();
272         }
273 
274     }
275 
test(String loggerName)276     public static void test(String loggerName) throws Exception {
277         System.out.println("Starting test for " + loggerName);
278         test("m.readConfiguration()", (m) -> m.readConfiguration());
279         test("m.readConfiguration(new ByteArrayInputStream(new byte[0]))",
280                 (m) -> m.readConfiguration(new ByteArrayInputStream(new byte[0])));
281         System.out.println("Test passed for " + loggerName);
282     }
283 
test(String testName, ThrowingConsumer<LogManager, IOException> readConfiguration)284     public static void test(String testName,
285             ThrowingConsumer<LogManager, IOException> readConfiguration) throws Exception {
286 
287 
288         System.out.println("\nBEGIN " + testName);
289         LogManager m = LogManager.getLogManager();
290 
291         final TestConfigurationListener l1 = new TestConfigurationListener("l#1");
292         final TestConfigurationListener l2 = new TestConfigurationListener("l#2");
293         final TestConfigurationListener l3 = new ThrowingConfigurationListener("l#3", false);
294         final TestConfigurationListener l4 = new ThrowingConfigurationListener("l#4", true);
295         final TestConfigurationListener l5 = new ThrowingConfigurationListener("l#5", false);
296 
297         final Set<String> expectedExceptions =
298                 Collections.unmodifiableSet(
299                         new HashSet<>(Arrays.asList(
300                                 l3.toString(), l4.toString(), l5.toString())));
301 
302         m.addConfigurationListener(l1);
303         m.addConfigurationListener(l2);
304         expect(l1, 0);
305         expect(l2, 0);
306 
307         readConfiguration.accept(m);
308         expect(l1, 1);
309         expect(l2, 1);
310         m.addConfigurationListener(l1);
311         expect(l1, 1);
312         expect(l2, 1);
313         readConfiguration.accept(m);
314         expect(l1, 2);
315         expect(l2, 2);
316         m.removeConfigurationListener(l1);
317         expect(l1, 2);
318         expect(l2, 2);
319         readConfiguration.accept(m);
320         expect(l1, 2);
321         expect(l2, 3);
322         m.removeConfigurationListener(l1);
323         expect(l1, 2);
324         expect(l2, 3);
325         readConfiguration.accept(m);
326         expect(l1, 2);
327         expect(l2, 4);
328         m.removeConfigurationListener(l2);
329         expect(l1, 2);
330         expect(l2, 4);
331         readConfiguration.accept(m);
332         expect(l1, 2);
333         expect(l2, 4);
334 
335         // l1 and l2 should no longer be present: this should not fail...
336         m.removeConfigurationListener(l1);
337         m.removeConfigurationListener(l1);
338         m.removeConfigurationListener(l2);
339         m.removeConfigurationListener(l2);
340         expect(l1, 2);
341         expect(l2, 4);
342 
343         readConfiguration.accept(m);
344         expect(l1, 2);
345         expect(l2, 4);
346 
347         // add back l1 and l2
348         m.addConfigurationListener(l1);
349         m.addConfigurationListener(l2);
350         expect(l1, 2);
351         expect(l2, 4);
352 
353         readConfiguration.accept(m);
354         expect(l1, 3);
355         expect(l2, 5);
356 
357         m.removeConfigurationListener(l1);
358         m.removeConfigurationListener(l2);
359         expect(l1, 3);
360         expect(l2, 5);
361 
362         readConfiguration.accept(m);
363         expect(l1, 3);
364         expect(l2, 5);
365 
366         // Check the behavior when listeners throw exceptions
367         // l3, l4, and l5 will throw an error/exception.
368         // The first that is raised will be propagated, after all listeners
369         // have been invoked. The other exceptions will be added to the
370         // suppressed list.
371         //
372         // We will check that all listeners have been invoked and that we
373         // have the set of 3 exceptions expected from l3, l4, l5.
374         //
375         m.addConfigurationListener(l4);
376         m.addConfigurationListener(l1);
377         m.addConfigurationListener(l2);
378         m.addConfigurationListener(l3);
379         m.addConfigurationListener(l5);
380 
381         try {
382             readConfiguration.accept(m);
383             throw new RuntimeException("Excpected exception/error not raised");
384         } catch(ConfigurationListenerException | ConfigurationListenerError t) {
385             final Set<String> received = new HashSet<>();
386             received.add(t.toString());
387             for (Throwable s : t.getSuppressed()) {
388                 received.add(s.toString());
389             }
390             System.out.println("Received exceptions: " + received);
391             if (!expectedExceptions.equals(received)) {
392                 throw new RuntimeException(
393                         "List of received exceptions differs from expected:"
394                                 + "\n\texpected: " + expectedExceptions
395                                 + "\n\treceived: " + received);
396             }
397         }
398         expect(l1, 4);
399         expect(l2, 6);
400 
401         m.removeConfigurationListener(l1);
402         m.removeConfigurationListener(l2);
403         m.removeConfigurationListener(l3);
404         m.removeConfigurationListener(l4);
405         m.removeConfigurationListener(l5);
406         readConfiguration.accept(m);
407         expect(l1, 4);
408         expect(l2, 6);
409 
410 
411         try {
412             m.addConfigurationListener(null);
413             throw new RuntimeException(
414                     "addConfigurationListener(null): Expected NPE not thrown.");
415         } catch (NullPointerException npe) {
416             System.out.println("Got expected NPE: "+npe);
417         }
418 
419         try {
420             m.removeConfigurationListener(null);
421             throw new RuntimeException(
422                     "removeConfigurationListener(null): Expected NPE not thrown.");
423         } catch (NullPointerException npe) {
424             System.out.println("Got expected NPE: "+npe);
425         }
426 
427         System.out.println("END " + testName+"\n");
428 
429     }
430 
431 
432     static final class PermissionsBuilder {
433         final Permissions perms;
PermissionsBuilder()434         public PermissionsBuilder() {
435             this(new Permissions());
436         }
PermissionsBuilder(Permissions perms)437         public PermissionsBuilder(Permissions perms) {
438             this.perms = perms;
439         }
add(Permission p)440         public PermissionsBuilder add(Permission p) {
441             perms.add(p);
442             return this;
443         }
addAll(PermissionCollection col)444         public PermissionsBuilder addAll(PermissionCollection col) {
445             if (col != null) {
446                 for (Enumeration<Permission> e = col.elements(); e.hasMoreElements(); ) {
447                     perms.add(e.nextElement());
448                 }
449             }
450             return this;
451         }
toPermissions()452         public Permissions toPermissions() {
453             final PermissionsBuilder builder = new PermissionsBuilder();
454             builder.addAll(perms);
455             return builder.perms;
456         }
457     }
458 
459     public static class SimplePolicy extends Policy {
460 
461         static final Policy DEFAULT_POLICY = Policy.getPolicy();
462 
463         final Permissions permissions;
SimplePolicy(TestCase test)464         public SimplePolicy(TestCase test) {
465             permissions = new Permissions();
466             if (test != TestCase.PERMISSION) {
467                 permissions.add(new LoggingPermission("control", null));
468                 permissions.add(new PropertyPermission("java.util.logging.config.class", "read"));
469                 permissions.add(new PropertyPermission("java.util.logging.config.file", "read"));
470                 permissions.add(new PropertyPermission("java.home", "read"));
471                 permissions.add(new FilePermission("<<ALL FILES>>", "read"));
472             }
473         }
474 
475         @Override
implies(ProtectionDomain domain, Permission permission)476         public boolean implies(ProtectionDomain domain, Permission permission) {
477             return permissions.implies(permission) || DEFAULT_POLICY.implies(domain, permission);
478         }
479 
480         @Override
getPermissions(CodeSource codesource)481         public PermissionCollection getPermissions(CodeSource codesource) {
482             return new PermissionsBuilder().addAll(permissions).toPermissions();
483         }
484 
485         @Override
getPermissions(ProtectionDomain domain)486         public PermissionCollection getPermissions(ProtectionDomain domain) {
487             return new PermissionsBuilder().addAll(permissions).toPermissions();
488         }
489     }
490 
491 }
492