1 /*
   2  * Copyright (c) 2005, 2014, 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 5106721
  27  * @summary Check the NotificationAccessController methods are properly called.
  28  * @author Luis-Miguel Alventosa
  29  * @run clean NotificationAccessControllerTest
  30  * @run build NotificationAccessControllerTest
  31  * @run main NotificationAccessControllerTest
  32  */
  33 
  34 import com.sun.jmx.remote.security.NotificationAccessController;
  35 import java.util.ArrayList;
  36 import java.util.Collections;
  37 import java.util.HashMap;
  38 import java.util.List;
  39 import java.util.Map;
  40 import java.util.concurrent.CopyOnWriteArrayList;
  41 import java.util.concurrent.Semaphore;
  42 import javax.management.MBeanServer;
  43 import javax.management.MBeanServerConnection;
  44 import javax.management.MBeanServerFactory;
  45 import javax.management.Notification;
  46 import javax.management.NotificationBroadcasterSupport;
  47 import javax.management.NotificationListener;
  48 import javax.management.ObjectName;
  49 import javax.management.remote.JMXAuthenticator;
  50 import javax.management.remote.JMXConnector;
  51 import javax.management.remote.JMXConnectorFactory;
  52 import javax.management.remote.JMXConnectorServer;
  53 import javax.management.remote.JMXConnectorServerFactory;
  54 import javax.management.remote.JMXPrincipal;
  55 import javax.management.remote.JMXServiceURL;
  56 import javax.security.auth.Subject;
  57 
  58 public class NotificationAccessControllerTest {
  59 
  60     public class NAC implements NotificationAccessController {
  61         private final boolean throwException;
  62         public NAC(boolean throwException) {
  63             this.throwException = throwException;
  64         }
  65 
  66         @Override
  67         public void addNotificationListener(
  68             String connectionId,
  69             ObjectName name,
  70             Subject subject)
  71             throws SecurityException {
  72             echo("addNotificationListener:");
  73             echo("\tconnectionId: " +  connectionId);
  74             echo("\tname: " +  name);
  75             echo("\tsubject: " +
  76                  (subject == null ? null : subject.getPrincipals()));
  77             if (throwException)
  78                 if (name.getCanonicalName().equals("domain:name=1,type=NB")
  79                     &&
  80                     subject != null
  81                     &&
  82                     subject.getPrincipals().contains(new JMXPrincipal("role")))
  83                     throw new SecurityException();
  84         }
  85 
  86         @Override
  87         public void removeNotificationListener(
  88             String connectionId,
  89             ObjectName name,
  90             Subject subject)
  91             throws SecurityException {
  92             echo("removeNotificationListener:");
  93             echo("\tconnectionId: " +  connectionId);
  94             echo("\tname: " +  name);
  95             echo("\tsubject: " +
  96                  (subject == null ? null : subject.getPrincipals()));
  97             if (throwException)
  98                 if (name.getCanonicalName().equals("domain:name=2,type=NB")
  99                     &&
 100                     subject != null
 101                     &&
 102                     subject.getPrincipals().contains(new JMXPrincipal("role")))
 103                     throw new SecurityException();
 104         }
 105 
 106         @Override
 107         public void fetchNotification(
 108             String connectionId,
 109             ObjectName name,
 110             Notification notification,
 111             Subject subject)
 112             throws SecurityException {
 113             echo("fetchNotification:");
 114             echo("\tconnectionId: " +  connectionId);
 115             echo("\tname: " +  name);
 116             echo("\tnotification: " +  notification);
 117             echo("\tsubject: " +
 118                  (subject == null ? null : subject.getPrincipals()));
 119             if (!throwException)
 120                 if (name.getCanonicalName().equals("domain:name=2,type=NB")
 121                     &&
 122                     subject != null
 123                     &&
 124                     subject.getPrincipals().contains(new JMXPrincipal("role")))
 125                     throw new SecurityException();
 126         }
 127     }
 128 
 129     public class CustomJMXAuthenticator implements JMXAuthenticator {
 130         @Override
 131         public Subject authenticate(Object credentials) {
 132             String role = ((String[]) credentials)[0];
 133             echo("\nCreate principal with name = " + role);
 134             return new Subject(true,
 135                                Collections.singleton(new JMXPrincipal(role)),
 136                                Collections.EMPTY_SET,
 137                                Collections.EMPTY_SET);
 138         }
 139     }
 140 
 141     public interface NBMBean {
 142         public void emitNotification(int seqnum, ObjectName name);
 143     }
 144 
 145     public static class NB
 146         extends NotificationBroadcasterSupport
 147         implements NBMBean {
 148         @Override
 149         public void emitNotification(int seqnum, ObjectName name) {
 150             if (name == null) {
 151                 sendNotification(new Notification("nb", this, seqnum));
 152             } else {
 153                 sendNotification(new Notification("nb", name, seqnum));
 154             }
 155         }
 156     }
 157 
 158     public class Listener implements NotificationListener {
 159         public final List<Notification> notifs = new CopyOnWriteArrayList<>();
 160 
 161         private final Semaphore s;
 162         public Listener(Semaphore s) {
 163             this.s = s;
 164         }
 165         @Override
 166         public void handleNotification(Notification n, Object h) {
 167             echo("handleNotification:");
 168             echo("\tNotification = " + n);
 169             echo("\tNotification.SeqNum = " + n.getSequenceNumber());
 170             echo("\tHandback = " + h);
 171             notifs.add(n);
 172             s.release();
 173         }
 174     }
 175 
 176     /**
 177      * Check received notifications
 178      */
 179     public int checkNotifs(int size,
 180                            List<Notification> received,
 181                            List<ObjectName> expected) {
 182         if (received.size() != size) {
 183             echo("Error: expecting " + size + " notifications, got " +
 184                     received.size());
 185             return 1;
 186         } else {
 187             for (Notification n : received) {
 188                 echo("Received notification: " + n);
 189                 if (!n.getType().equals("nb")) {
 190                     echo("Notification type must be \"nb\"");
 191                     return 1;
 192                 }
 193                 ObjectName o = (ObjectName) n.getSource();
 194                 int index = (int) n.getSequenceNumber();
 195                 ObjectName nb = expected.get(index);
 196                 if (!o.equals(nb)) {
 197                     echo("Notification source must be " + nb);
 198                     return 1;
 199                 }
 200             }
 201         }
 202         return 0;
 203     }
 204 
 205     /**
 206      * Run test
 207      */
 208     public int runTest(boolean enableChecks, boolean throwException)
 209         throws Exception {
 210 
 211         echo("\n=-=-= " + (enableChecks ? "Enable" : "Disable") +
 212              " notification access control checks " +
 213              (!enableChecks ? "" : (throwException ? ": add/remove " :
 214              ": fetch ")) + "=-=-=");
 215 
 216         JMXConnectorServer server = null;
 217         JMXConnector client = null;
 218 
 219         /*
 220         * (!enableChecks)
 221         * - List must contain three notifs from sources nb1, nb2 and nb3
 222         * (enableChecks && !throwException)
 223         * - List must contain one notif from source nb1
 224         * (enableChecks && throwException)
 225         * - List must contain two notifs from sources nb2 and nb3
 226         */
 227         final int expected_notifs =
 228             (!enableChecks ? 3 : (throwException ? 2 : 1));
 229 
 230         // Create a new MBeanServer
 231         //
 232         final MBeanServer mbs = MBeanServerFactory.createMBeanServer();
 233 
 234         try {
 235             // Create server environment map
 236             //
 237             final Map<String,Object> env = new HashMap<>();
 238             env.put("jmx.remote.authenticator", new CustomJMXAuthenticator());
 239             if (enableChecks) {
 240                 env.put("com.sun.jmx.remote.notification.access.controller",
 241                         new NAC(throwException));
 242             }
 243 
 244             // Create the JMXServiceURL
 245             //
 246             final JMXServiceURL url = new JMXServiceURL("service:jmx:rmi://");
 247 
 248             // Create a JMXConnectorServer
 249             //
 250             server = JMXConnectorServerFactory.newJMXConnectorServer(url,
 251                                                                      env,
 252                                                                      mbs);
 253 
 254             // Start the JMXConnectorServer
 255             //
 256             server.start();
 257 
 258             // Create server environment map
 259             //
 260             final Map<String,Object> cenv = new HashMap<>();
 261             String[] credentials = new String[] { "role" , "password" };
 262             cenv.put("jmx.remote.credentials", credentials);
 263 
 264             // Create JMXConnector and connect to JMXConnectorServer
 265             //
 266             client = JMXConnectorFactory.connect(server.getAddress(), cenv);
 267 
 268             // Get non-secure MBeanServerConnection
 269             //
 270             final MBeanServerConnection mbsc =
 271                 client.getMBeanServerConnection();
 272 
 273             // Create NB MBean
 274             //
 275             ObjectName nb1 = ObjectName.getInstance("domain:type=NB,name=1");
 276             ObjectName nb2 = ObjectName.getInstance("domain:type=NB,name=2");
 277             ObjectName nb3 = ObjectName.getInstance("domain:type=NB,name=3");
 278             mbsc.createMBean(NB.class.getName(), nb1);
 279             mbsc.createMBean(NB.class.getName(), nb2);
 280             mbsc.createMBean(NB.class.getName(), nb3);
 281 
 282             // Add notification listener
 283             //
 284             Semaphore s = new Semaphore(0);
 285 
 286             Listener li = new Listener(s);
 287             try {
 288                 mbsc.addNotificationListener(nb1, li, null, null);
 289                 if (enableChecks && throwException) {
 290                     echo("Didn't get expected exception");
 291                     return 1;
 292                 }
 293             } catch (SecurityException e) {
 294                 if (enableChecks && throwException) {
 295                     echo("Got expected exception: " + e);
 296                 } else {
 297                     echo("Got unexpected exception: " + e);
 298                     return 1;
 299                 }
 300             }
 301             mbsc.addNotificationListener(nb2, li, null, null);
 302 
 303             System.out.println("\n+++ Expecting to receive " + expected_notifs +
 304                                " notification" + (expected_notifs > 1 ? "s" : "") +
 305                                " +++");
 306             // Invoke the "sendNotification" method
 307             //
 308             mbsc.invoke(nb1, "emitNotification",
 309                 new Object[] {0, null},
 310                 new String[] {"int", "javax.management.ObjectName"});
 311             mbsc.invoke(nb2, "emitNotification",
 312                 new Object[] {1, null},
 313                 new String[] {"int", "javax.management.ObjectName"});
 314             mbsc.invoke(nb2, "emitNotification",
 315                 new Object[] {2, nb3},
 316                 new String[] {"int", "javax.management.ObjectName"});
 317 
 318             // Wait for notifications to be emitted
 319             //
 320             s.acquire(expected_notifs);
 321 
 322             // Remove notification listener
 323             //
 324             if (!throwException)
 325                 mbsc.removeNotificationListener(nb1, li);
 326             try {
 327                 mbsc.removeNotificationListener(nb2, li);
 328                 if (enableChecks && throwException) {
 329                     echo("Didn't get expected exception");
 330                     return 1;
 331                 }
 332             } catch (SecurityException e) {
 333                 if (enableChecks && throwException) {
 334                     echo("Got expected exception: " + e);
 335                 } else {
 336                     echo("Got unexpected exception: " + e);
 337                     return 1;
 338                 }
 339             }
 340 
 341             int result = 0;
 342             List<ObjectName> sources = new ArrayList();
 343             sources.add(nb1);
 344             sources.add(nb2);
 345             sources.add(nb3);
 346             result = checkNotifs(expected_notifs, li.notifs, sources);
 347             if (result > 0) {
 348                 return result;
 349             }
 350         } catch (Exception e) {
 351             echo("Failed to perform operation: " + e);
 352             e.printStackTrace();
 353             return 1;
 354         } finally {
 355             // Close the connection
 356             //
 357             if (client != null)
 358                 client.close();
 359 
 360             // Stop the connector server
 361             //
 362             if (server != null)
 363                 server.stop();
 364 
 365             // Release the MBeanServer
 366             //
 367             if (mbs != null)
 368                 MBeanServerFactory.releaseMBeanServer(mbs);
 369         }
 370 
 371         return 0;
 372     }
 373 
 374     /*
 375      * Print message
 376      */
 377     private static void echo(String message) {
 378         System.out.println(message);
 379     }
 380 
 381     public static void main(String[] args) throws Exception {
 382 
 383         System.out.println("\nTest notification access control.");
 384 
 385         NotificationAccessControllerTest nact =
 386             new NotificationAccessControllerTest();
 387 
 388         int error = 0;
 389 
 390         error += nact.runTest(false, false);
 391 
 392         error += nact.runTest(true, false);
 393 
 394         error += nact.runTest(true, true);
 395 
 396         if (error > 0) {
 397             final String msg = "\nTest FAILED! Got " + error + " error(s)";
 398             System.out.println(msg);
 399             throw new IllegalArgumentException(msg);
 400         } else {
 401             System.out.println("\nTest PASSED!");
 402         }
 403     }
 404 }