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 }