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