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