1/*
2 * Copyright (c) 2003, 2015, 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/*
25 * @test
26 * @bug 8058865
27 * @summary Checks various authentication behavior from remote jmx client
28 * @author Olivier Lagneau
29 * @modules java.management.rmi
30 * @library /lib/testlibrary
31 * @compile Simple.java
32 * @run main/othervm/timeout=300 -DDEBUG_STANDARD -Dusername=username1 -Dpassword=password1 AuthorizationTest -server -mapType x.access.file;x.password.file -populate -client -mapType credentials
33 * @run main/othervm/timeout=300 -DDEBUG_STANDARD -Dusername=username2 -Dpassword=password2 AuthorizationTest -server -mapType x.access.file;x.password.file -populate -client -mapType credentials -expectedCreateException -expectedSetException -expectedInvokeException
34 * @run main/othervm/timeout=300 -DDEBUG_STANDARD -Dusername=username6 -Dpassword=password6 AuthorizationTest -server -mapType x.access.file;x.password.file -populate -client -mapType credentials -expectedCreateException -expectedGetException -expectedSetException -expectedInvokeException
35 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username1 -Dpassword=password1 AuthorizationTest -server -mapType x.password.file -populate -client -mapType credentials
36 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username3 -Dpassword=password3 AuthorizationTest -server -mapType x.password.file -populate -client -mapType credentials -expectedGetException
37 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username5 -Dpassword=password5 AuthorizationTest -server -mapType x.password.file -populate -client -mapType credentials -expectedCreateException -expectedGetException -expectedSetException -expectedInvokeException
38 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username6 -Dpassword=password6 AuthorizationTest -server -mapType x.password.file -populate -client -mapType credentials -expectedCreateException -expectedGetException -expectedSetException -expectedInvokeException
39 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username1 -Dpassword=password1 AuthorizationTest -server -mapType x.access.file;x.password.file -populate -client -mapType credentials
40 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username2 -Dpassword=password2 AuthorizationTest -server -mapType x.access.file;x.password.file -populate -client -mapType credentials -expectedCreateException -expectedSetException -expectedInvokeException
41 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username3 -Dpassword=password3 AuthorizationTest -server -mapType x.access.file;x.password.file -populate -client -mapType credentials -expectedCreateException -expectedGetException -expectedSetException -expectedInvokeException
42 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username4 -Dpassword=password4 AuthorizationTest -server -mapType x.access.file;x.password.file -populate -client -mapType credentials -expectedGetException -expectedSetException
43 * @run main/othervm/timeout=300/policy=java.policy.authorization -DDEBUG_STANDARD -Dusername=username5 -Dpassword=password5 AuthorizationTest -server -mapType x.access.file;x.password.file -populate -client -mapType credentials -expectedCreateException -expectedGetException -expectedSetException -expectedInvokeException
44 */
45
46import java.io.File;
47import java.util.Map ;
48import java.util.HashMap ;
49import java.util.List;
50import java.util.ArrayList;
51import java.util.Arrays;
52
53import java.lang.management.ManagementFactory;
54
55import javax.management.MBeanServer;
56import javax.management.MBeanServerFactory ;
57import javax.management.MBeanServerConnection;
58import javax.management.remote.JMXConnector;
59import javax.management.remote.JMXConnectorFactory;
60import javax.management.remote.JMXConnectorServer;
61import javax.management.remote.JMXConnectorServerFactory;
62import javax.management.remote.JMXServiceURL;
63
64import javax.management.Attribute ;
65import javax.management.ObjectName ;
66
67import jdk.testlibrary.ProcessTools;
68import jdk.testlibrary.JDKToolFinder;
69
70public class AuthorizationTest {
71
72    static final String SERVER_CLASS_NAME = "AuthorizationTest";
73    static final String CLIENT_CLASS_NAME = "AuthorizationTest$ClientSide";
74    static final String CLIENT_CLASS_MAIN = CLIENT_CLASS_NAME;
75
76    static final String USERNAME_PROPERTY = "username";
77    static final String PASSWORD_PROPERTY = "password";
78
79    private JMXConnectorServer cs;
80
81    /*
82     * First Debug properties and arguments are collect in expected
83     * map  (argName, value) format, then calls original test's run method.
84     */
85    public static void main(String args[]) throws Exception {
86
87        System.out.println("=================================================");
88
89        // Parses parameters
90        Utils.parseDebugProperties();
91
92        // Supported parameters list format is :
93        // "MainClass [-server <param-spec> ...] [-client <param-spec> ...]
94        // with <param-spec> either "-parami valuei" or "-parami"
95        HashMap<String, Object> serverMap = new HashMap<>() ;
96        int clientArgsIndex =
97            Utils.parseServerParameters(args, SERVER_CLASS_NAME, serverMap);
98
99        // Extract and records client params
100        String[] clientParams = null;
101        if (clientArgsIndex < args.length) {
102            int clientParamsSize = args.length - clientArgsIndex;
103            clientParams = new String[clientParamsSize];
104            System.arraycopy(args, clientArgsIndex, clientParams, 0, clientParamsSize);
105        } else {
106            clientParams = new String[0];
107        }
108
109        // Run test
110        AuthorizationTest test = new AuthorizationTest();
111        test.run(serverMap, clientParams);
112
113    }
114
115    /*
116     * Create the MBeansServer side of the test and returns its address
117     */
118    private JMXServiceURL createServerSide(Map<String, Object> serverMap)
119    throws Exception {
120        final int NINETY_SECONDS = 90;
121
122        System.out.println("AuthorizationTest::createServerSide: Start") ;
123
124        MBeanServer mbs = MBeanServerFactory.newMBeanServer();
125        JMXServiceURL url = new JMXServiceURL("rmi", null, 0);
126
127        // Creates connection environment from server side params
128        HashMap<String, Object> env = new HashMap<>();
129        String value = null;
130
131        if ((value = (String)serverMap.get("-mapType")) != null) {
132            if (value.contains("x.access.file")) {
133                String accessFileStr = System.getProperty("test.src") +
134                    File.separator + "access.properties";
135                env.put("jmx.remote.x.access.file", accessFileStr);
136                System.out.println("Added " + accessFileStr + " file as jmx.remote.x.access.file");
137            }
138            if (value.contains("x.password.file")) {
139                String passwordFileStr = System.getProperty("test.src") +
140                    File.separator + "password.properties";
141                env.put("jmx.remote.x.password.file", passwordFileStr);
142                System.out.println("Added " + passwordFileStr + " file as jmx.remote.x.password.file");
143            }
144        }
145
146        if (serverMap.containsKey("-populate")) {
147            String populateClassName = "Simple";
148            ObjectName on =
149                new ObjectName("defaultDomain:class=Simple");
150
151            Utils.debug(Utils.DEBUG_STANDARD, "create and register Simple MBean") ;
152            mbs.createMBean(populateClassName, on);
153        }
154
155        cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
156        cs.start();
157
158        Utils.waitReady(cs, NINETY_SECONDS);
159
160        JMXServiceURL addr = cs.getAddress();
161
162        System.out.println("AuthorizationTest::createServerSide: Done.") ;
163
164        return addr;
165    }
166
167    /*
168     * Creating command-line for running subprocess JVM:
169     *
170     * JVM command line is like:
171     * {test_jdk}/bin/java {defaultopts} -cp {test.class.path} {testopts} main
172     *
173     * {defaultopts} are the default java options set by the framework.
174     *
175     */
176    private List<String> buildCommandLine(String args[]) {
177        List<String> opts = new ArrayList<>();
178        opts.add(JDKToolFinder.getJDKTool("java"));
179        opts.addAll(Arrays.asList(jdk.testlibrary.Utils.getTestJavaOpts()));
180
181        String usernameValue = System.getProperty(USERNAME_PROPERTY);
182        if (usernameValue != null) {
183            opts.add("-D" + USERNAME_PROPERTY + "=" + usernameValue);
184        }
185        String passwordValue = System.getProperty(PASSWORD_PROPERTY);
186        if (passwordValue != null) {
187            opts.add("-D" + PASSWORD_PROPERTY + "=" + passwordValue);
188        }
189
190        opts.add("-cp");
191        opts.add(System.getProperty("test.class.path", "test.class.path"));
192        opts.add(CLIENT_CLASS_MAIN);
193        opts.addAll(Arrays.asList(args));
194        return opts;
195    }
196
197    /**
198     * Runs AuthorizationTest$ClientSide with the passed options and redirects
199     * subprocess standard I/O to the current (parent) process. This provides a
200     * trace of what happens in the subprocess while it is runnning (and before
201     * it terminates).
202     *
203     * @param serviceUrlStr string representing the JMX service Url to connect to.
204     */
205    private int runClientSide(String args[], String serviceUrlStr) throws Exception {
206
207        // Building command-line
208        List<String> opts = buildCommandLine(args);
209        opts.add("-serviceUrl");
210        opts.add(serviceUrlStr);
211
212        // Launch separate JVM subprocess
213        int exitCode = 0;
214        String[] optsArray = opts.toArray(new String[0]);
215        ProcessBuilder pb = new ProcessBuilder(optsArray);
216        Process p = ProcessTools.startProcess("AuthorizationTest$ClientSide", pb);
217
218        // Handling end of subprocess
219        try {
220            exitCode = p.waitFor();
221            if (exitCode != 0) {
222                System.out.println(
223                    "Subprocess unexpected exit value of [" + exitCode +
224                    "]. Expected 0.\n");
225            }
226        } catch (InterruptedException e) {
227            System.out.println("Parent process interrupted with exception : \n " + e + " :" );
228
229            // Parent thread unknown state, killing subprocess.
230            p.destroyForcibly();
231
232            throw new RuntimeException(
233                "Parent process interrupted with exception : \n " + e + " :" );
234
235        } finally {
236            if (p.isAlive()) {
237                p.destroyForcibly();
238            }
239            return exitCode;
240        }
241
242     }
243
244    public void run(Map<String, Object> serverArgs, String clientArgs[]) {
245
246        System.out.println("AuthorizationTest::run: Start") ;
247        int errorCount = 0;
248
249        try {
250            // Initialise the server side
251            JMXServiceURL urlToUse = createServerSide(serverArgs);
252
253            // Run client side
254            errorCount = runClientSide(clientArgs, urlToUse.toString());
255
256            if ( errorCount == 0 ) {
257                System.out.println("AuthorizationTest::run: Done without any error") ;
258            } else {
259                System.out.println("AuthorizationTest::run: Done with "
260                        + errorCount
261                        + " error(s)") ;
262                throw new RuntimeException("errorCount = " + errorCount);
263            }
264
265            cs.stop();
266
267        } catch(Exception e) {
268            throw new RuntimeException(e);
269        }
270
271    }
272
273    private static class ClientSide {
274
275        private JMXConnector cc = null;
276        private MBeanServerConnection mbsc = null;
277
278        public static void main(String args[]) throws Exception {
279
280            // Parses parameters
281            Utils.parseDebugProperties();
282
283            // Supported parameters list format is : "MainClass [-client <param-spec> ...]
284            // with <param-spec> either "-parami valuei" or "-parami"
285            HashMap<String, Object> clientMap = new HashMap<>() ;
286            Utils.parseClientParameters(args, CLIENT_CLASS_NAME, clientMap);
287
288            // Run test
289            ClientSide test = new ClientSide();
290            test.run(clientMap);
291
292        }
293
294        public void run(Map<String, Object> args) {
295
296            int errorCount = 0 ;
297
298            try {
299                boolean expectedCreateException =
300                        (args.containsKey("-expectedCreateException")) ? true : false ;
301                boolean expectedGetException =
302                        (args.containsKey("-expectedGetException")) ? true : false ;
303                boolean expectedSetException =
304                        (args.containsKey("-expectedSetException")) ? true : false ;
305                boolean expectedInvokeException =
306                        (args.containsKey("-expectedInvokeException")) ? true : false ;
307                // JSR262 (see bug 6440374)
308                // There is no special JSR262 protocol operation for connect.
309                // The first request sent initiate the connection.
310                // In the JSR262 current implementation, getDefaultDomain is sent to
311                // the server in order to get the server part of the connection ID.
312                // => the connection may fail if no access permission on get requests.
313                boolean expectedConnectException =
314                        (args.containsKey("-expectedConnectException")) ? true : false ;
315                // Before connection,
316                // remove the element of the Map with null values (not supported by RMI)
317                // See bug 4982668
318                args.remove("-expectedCreateException");
319                args.remove("-expectedGetException");
320                args.remove("-expectedSetException");
321                args.remove("-expectedInvokeException");
322                args.remove("-expectedConnectException");
323
324
325                // Here do connect to the JMX Server
326                String username = System.getProperty("username");
327                Utils.debug(Utils.DEBUG_STANDARD,
328                    "ClientSide::run: CONNECT on behalf of \"" + username + "\"");
329                doConnect(args, expectedConnectException);
330
331                // If the connection did not fail, perform some requests.
332                // At this stage the mbeanserver connection is up and running
333                if (mbsc != null) {
334                    ObjectName on = new ObjectName("defaultDomain:class=Simple");
335
336                    // Create request
337                    Utils.debug(Utils.DEBUG_STANDARD,
338                        "ClientSide::run: CREATE on behalf of \"" +
339                        username + "\"");
340                    errorCount += doCreateRequest(mbsc,
341                        new ObjectName("defaultDomain:class=Simple,user=" + username),
342                        expectedCreateException);
343
344                    // Get request
345                    Utils.debug(Utils.DEBUG_STANDARD,
346                        "ClientSide::run: GET on behalf of \"" +
347                        username + "\"");
348                    errorCount += doGetRequest(mbsc, on, expectedGetException);
349
350                    // Set request
351                    Utils.debug(Utils.DEBUG_STANDARD,
352                        "ClientSide::run: SET on behalf of \"" +
353                        username + "\"");
354                    errorCount += doSetRequest(mbsc, on, expectedSetException);
355
356                    // Invoke request
357                    Utils.debug(Utils.DEBUG_STANDARD,
358                        "ClientSide::run: INVOKE on behalf of \"" +
359                        username + "\"");
360                    errorCount += doInvokeRequest(mbsc, on, expectedInvokeException);
361                }
362
363            } catch(Exception e) {
364                Utils.printThrowable(e, true) ;
365                errorCount++;
366            } finally {
367                // Terminate the JMX Client
368                try {
369                    cc.close();
370                } catch (Exception e) {
371                    Utils.printThrowable(e, true) ;
372                    errorCount++;
373                }
374            }
375
376            System.out.println("ClientSide::run: Done") ;
377
378            // Handle result
379            if (errorCount == 0) {
380                System.out.println("ClientSide::run: (OK) authorization test succeeded.");
381            } else {
382                String message = "AuthorizationTest$ClientSide::run: (ERROR) " +
383                        " authorization test failed with " +
384                        errorCount + " error(s)";
385                System.out.println(message);
386                throw new RuntimeException(message);
387            }
388        }
389
390        protected void doConnect(Map<String, Object> args,
391                                 boolean expectedException) {
392
393            String msgTag = "ClientSide::doConnect";
394            boolean throwRuntimeException = false;
395            String message = "";
396
397            try {
398                Utils.debug(Utils.DEBUG_STANDARD,
399                    "ClientSide::doConnect: Connect the client");
400
401                // Collect connection environment
402                HashMap<String, Object> env = new HashMap<>();
403
404                Object value = args.get("-mapType");
405                if (value != null) {
406                    String username = System.getProperty("username");
407                    String password = System.getProperty("password");
408                    Utils.debug(Utils.DEBUG_STANDARD,
409                        msgTag + "add \"jmx.remote.credentials\" = \"" +
410                        username + "\", \"" + password + "\"");
411                    env.put("jmx.remote.credentials",
412                        new String[] { username , password });
413                }
414
415                // Get a connection to remote mbean server
416                JMXServiceURL addr = new JMXServiceURL((String)args.get("-serviceUrl"));
417                cc = JMXConnectorFactory.connect(addr,env);
418                mbsc = cc.getMBeanServerConnection();
419
420                if (expectedException) {
421                    message = "ClientSide::doConnect: (ERROR) " +
422                        "Connect did not fail with expected SecurityException";
423                    System.out.println(message);
424                    throwRuntimeException = true;
425                } else {
426                    System.out.println("ClientSide::doConnect: (OK) Connect succeed");
427                }
428            } catch(Exception e) {
429                Utils.printThrowable(e, true);
430                if (expectedException) {
431                    if (e instanceof java.lang.SecurityException) {
432                        System.out.println("ClientSide::doConnect: (OK) " +
433                            "Connect failed with expected SecurityException");
434                    } else {
435                        message = "ClientSide::doConnect: (ERROR) " +
436                            "Create failed with " + e.getClass() +
437                            " instead of expected SecurityException";
438                        System.out.println(message);
439                        throwRuntimeException = true;
440                    }
441                } else {
442                    message = "ClientSide::doConnect: (ERROR) " +
443                        "Connect failed";
444                    System.out.println(message);
445                    throwRuntimeException = true;
446                }
447            }
448
449            // If the connection failed, or if the connection succeeded but should not,
450            // no need to go further => throw RuntimeException and exit the test
451            if (throwRuntimeException) {
452                throw new RuntimeException(message);
453            }
454        }
455
456        protected int doCreateRequest(MBeanServerConnection mbsc,
457                                      ObjectName on,
458                                      boolean expectedException) {
459            int errorCount = 0;
460
461            try {
462                Utils.debug(Utils.DEBUG_STANDARD,
463                    "ClientSide::doCreateRequest: Create and register the MBean") ;
464
465                mbsc.createMBean("Simple", on) ;
466
467                if (expectedException) {
468                    System.out.println("ClientSide::doCreateRequest: " +
469                        "(ERROR) Create did not fail with expected SecurityException");
470                    errorCount++;
471                } else {
472                    System.out.println("ClientSide::doCreateRequest: (OK) Create succeed") ;
473                }
474            } catch(Exception e) {
475                Utils.printThrowable(e, true) ;
476                if (expectedException) {
477                    if (e instanceof java.lang.SecurityException) {
478                        System.out.println("ClientSide::doCreateRequest: " +
479                            "(OK) Create failed with expected SecurityException") ;
480                    } else {
481                        System.out.println("ClientSide::doCreateRequest: " +
482                            "(ERROR) Create failed with " +
483                            e.getClass() + " instead of expected SecurityException");
484                        errorCount++;
485                    }
486                } else {
487                    System.out.println("ClientSide::doCreateRequest: " +
488                        "(ERROR) Create failed");
489                    errorCount++;
490                }
491            }
492            return errorCount;
493        }
494
495        protected int doGetRequest(MBeanServerConnection mbsc,
496                                   ObjectName on,
497                                   boolean expectedException) {
498            int errorCount = 0;
499
500            try {
501                Utils.debug(Utils.DEBUG_STANDARD,
502                    "ClientSide::doGetRequest: Get attributes of the MBean") ;
503
504                mbsc.getAttribute(on, "Attribute");
505
506                if (expectedException) {
507                    System.out.println("ClientSide::doGetRequest: " +
508                        "(ERROR) Get did not fail with expected SecurityException");
509                    errorCount++;
510                } else {
511                    System.out.println("ClientSide::doGetRequest: (OK) Get succeed") ;
512                }
513            } catch(Exception e) {
514                Utils.printThrowable(e, true) ;
515                if (expectedException) {
516                    if (e instanceof java.lang.SecurityException) {
517                        System.out.println("ClientSide::doGetRequest: " +
518                            "(OK) Get failed with expected SecurityException") ;
519                    } else {
520                        System.out.println("ClientSide::doGetRequest: " +
521                            "(ERROR) Get failed with " +
522                            e.getClass() + " instead of expected SecurityException");
523                        errorCount++;
524                    }
525                } else {
526                    System.out.println("ClientSide::doGetRequest: (ERROR) Get failed");
527                    errorCount++;
528                }
529            }
530
531            return errorCount;
532        }
533
534        protected int doSetRequest(MBeanServerConnection mbsc,
535                                   ObjectName on,
536                                   boolean expectedException) {
537            int errorCount = 0;
538
539            try {
540                Utils.debug(Utils.DEBUG_STANDARD,
541                    "ClientSide::doSetRequest: Set attributes of the MBean") ;
542
543                Attribute attribute = new Attribute("Attribute", "My value") ;
544                mbsc.setAttribute(on, attribute) ;
545
546                if (expectedException) {
547                    System.out.println("ClientSide::doSetRequest: " +
548                        "(ERROR) Set did not fail with expected SecurityException");
549                    errorCount++;
550                } else {
551                    System.out.println("ClientSide::doSetRequest: (OK) Set succeed") ;
552                }
553            } catch(Exception e) {
554                Utils.printThrowable(e, true) ;
555                if (expectedException) {
556                    if (e instanceof java.lang.SecurityException) {
557                        System.out.println("ClientSide::doSetRequest: " +
558                            "(OK) Set failed with expected SecurityException") ;
559                    } else {
560                        System.out.println("ClientSide::doSetRequest: " +
561                            "(ERROR) Set failed with " +
562                            e.getClass() + " instead of expected SecurityException");
563                        errorCount++;
564                    }
565                } else {
566                    System.out.println("ClientSide::doSetRequest: (ERROR) Set failed");
567                    errorCount++;
568                }
569            }
570            return errorCount;
571        }
572
573        protected int doInvokeRequest(MBeanServerConnection mbsc,
574                                      ObjectName on,
575                                      boolean expectedException) {
576            int errorCount = 0;
577
578            try {
579                Utils.debug(Utils.DEBUG_STANDARD,
580                    "ClientSide::doInvokeRequest: Invoke operations on the MBean") ;
581
582                mbsc.invoke(on, "operation", null, null) ;
583
584                if (expectedException) {
585                    System.out.println("ClientSide::doInvokeRequest: " +
586                        "(ERROR) Invoke did not fail with expected SecurityException");
587                    errorCount++;
588                } else {
589                    System.out.println("ClientSide::doInvokeRequest: (OK) Invoke succeed") ;
590                }
591            } catch(Exception e) {
592                Utils.printThrowable(e, true) ;
593                if (expectedException) {
594                    if (e instanceof java.lang.SecurityException) {
595                        System.out.println("ClientSide::doInvokeRequest: " +
596                            "(OK) Invoke failed with expected SecurityException") ;
597                    } else {
598                        System.out.println("ClientSide::doInvokeRequest: " +
599                            " (ERROR) Invoke failed with " +
600                            e.getClass() + " instead of expected SecurityException");
601                        errorCount++;
602                    }
603                } else {
604                    System.out.println("ClientSide::doInvokeRequest: " +
605                        "(ERROR) Invoke failed");
606                    errorCount++;
607                }
608            }
609            return errorCount;
610        }
611
612    }
613}
614