/* * JBoss, Home of Professional Open Source. * Copyright 2012, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.jmx.rbac; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.security.PrivilegedExceptionAction; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.InstanceNotFoundException; import javax.management.JMRuntimeException; import javax.management.MBeanServer; import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.NotificationListener; import javax.management.ObjectInstance; import javax.management.ObjectName; import org.jboss.as.controller.AccessAuditContext; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.CompositeOperationHandler; import org.jboss.as.controller.ManagementModel; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.PathElement; import org.jboss.as.controller.ProcessType; import org.jboss.as.controller.ResourceDefinition; import org.jboss.as.controller.RunningMode; import org.jboss.as.controller.RunningModeControl; import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; import org.jboss.as.controller.access.management.DelegatingConfigurableAuthorizer; import org.jboss.as.controller.access.management.ManagementSecurityIdentitySupplier; import org.jboss.as.controller.access.rbac.StandardRole; import org.jboss.as.controller.audit.AuditLogger; import org.jboss.as.controller.descriptions.ModelDescriptionConstants; import org.jboss.as.controller.extension.ExtensionRegistry; import org.jboss.as.controller.extension.ExtensionRegistryType; import org.jboss.as.controller.extension.RuntimeHostControllerInfoAccessor; import org.jboss.as.controller.operations.common.Util; import org.jboss.as.controller.operations.global.GlobalNotifications; import org.jboss.as.controller.operations.global.GlobalOperationHandlers; import org.jboss.as.controller.persistence.NullConfigurationPersister; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.as.controller.services.path.PathManagerService; import org.jboss.as.controller.services.path.PathResourceDefinition; import org.jboss.as.domain.management.CoreManagementResourceDefinition; import org.jboss.as.domain.management.access.AccessAuthorizationResourceDefinition; import org.jboss.as.domain.management.access.PrincipalResourceDefinition; import org.jboss.as.domain.management.access.RoleMappingResourceDefinition; import org.jboss.as.domain.management.audit.EnvironmentNameReader; import org.jboss.as.jmx.AuthorizingMBeanServer; import org.jboss.as.jmx.JMXExtension; import org.jboss.as.jmx.JMXSubsystemRootResource; import org.jboss.as.jmx.MBeanServerService; import org.jboss.as.jmx.test.util.AbstractControllerTestBase; import org.jboss.as.server.jmx.PluggableMBeanServer; import org.jboss.dmr.ModelNode; import org.jboss.dmr.ModelType; import org.jboss.msc.service.AbstractServiceListener; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceName; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.wildfly.security.auth.permission.LoginPermission; import org.wildfly.security.auth.realm.SimpleMapBackedSecurityRealm; import org.wildfly.security.auth.realm.SimpleRealmEntry; import org.wildfly.security.auth.server.RealmUnavailableException; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.auth.server.ServerAuthenticationContext; import org.xnio.IoUtils; /** * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> */ public abstract class JmxRbacTestCase extends AbstractControllerTestBase { volatile DelegatingConfigurableAuthorizer authorizer; volatile ManagementSecurityIdentitySupplier securityIdentitySupplier; volatile MBeanServer server; private static final String TEST_USER = "test"; private static final AttributeDefinition LAUNCH_TYPE = SimpleAttributeDefinitionBuilder.create("launch-type", ModelType.STRING) .setStorageRuntime() .build(); private static final String TYPE_STANDALONE = "STANDALONE"; private static final ObjectName OBJECT_NAME; private static final ObjectName OBJECT_NAME_MODEL; private static final ObjectName OBJECT_NAME2; static { try { OBJECT_NAME = new ObjectName("test:name=bean"); OBJECT_NAME_MODEL = new ObjectName("test:name=bean,type=model"); OBJECT_NAME2 = new ObjectName("test:name=bean2"); } catch (Exception e) { throw new RuntimeException(e); } } private final boolean enableRbac; private static SecurityDomain testDomain; JmxRbacTestCase(final boolean enableRbac){ this.enableRbac = enableRbac; } @BeforeClass public static void setupDomain() { Map<String, SimpleRealmEntry> entries = new HashMap<>(StandardRole.values().length); for (StandardRole role : StandardRole.values()) { entries.put(roleToUserName(role), new SimpleRealmEntry(Collections.emptyList())); } SimpleMapBackedSecurityRealm securityRealm = new SimpleMapBackedSecurityRealm(); securityRealm.setPasswordMap(entries); testDomain = SecurityDomain.builder() .setDefaultRealmName("Default") .addRealm("Default", securityRealm).build() .setPermissionMapper((p,r) -> new LoginPermission()) .build(); } @AfterClass public static void removeDomain() { testDomain = null; } @Before public void installMBeans() throws Exception { MBeanServer server = getMBeanServer(); server.registerMBean(new Bean(), OBJECT_NAME); server.registerMBean(new TestModelMBean(), OBJECT_NAME_MODEL); this.server = AuthorizingMBeanServer.wrap(server); } @After public void clearDependencies() throws Exception { authorizer = null; if (server.isRegistered(OBJECT_NAME)) { server.unregisterMBean(OBJECT_NAME); } if (server.isRegistered(OBJECT_NAME_MODEL)) { server.unregisterMBean(OBJECT_NAME_MODEL); } } @Test public void testUnauthorizedSensitiveMBeans() throws Exception { checkMBeanAccess(null, true); } @Test public void testUnauthorizedNotSensitiveMBeans() throws Exception { checkMBeanAccess(null, false); } @Test public void testSuperUserSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.SUPERUSER, true); } @Test public void testSuperUserNonSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.SUPERUSER, false); } @Test public void testAdministratorSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.ADMINISTRATOR, true); } @Test public void testAdministratorNonSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.ADMINISTRATOR, false); } @Test public void testAuditorSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.AUDITOR, true); } @Test public void testAuditorNonSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.AUDITOR, false); } @Test public void testDeployerSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.DEPLOYER, true); } @Test public void testDeployerNonSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.DEPLOYER, false); } @Test public void testMaintainerSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.MAINTAINER, true); } @Test public void testMaintainerNonSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.MAINTAINER, false); } @Test public void testMonitorSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.MONITOR, true); } @Test public void testMonitorNonSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.MONITOR, false); } @Test public void testOperatorSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.OPERATOR, true); } @Test public void testOperatorNonSensitiveMBeans() throws Exception { checkMBeanAccess(StandardRole.OPERATOR, false); } private static String roleToUserName(StandardRole role) { return TEST_USER + "_" + role.toString(); } private static SecurityIdentity roleToSecurityIdentity(StandardRole role) throws RealmUnavailableException { if (role == null) { return testDomain.getAnonymousSecurityIdentity(); } ServerAuthenticationContext authenticationContext = testDomain.createNewAuthenticationContext(); authenticationContext.setAuthenticationName(roleToUserName(role)); assertTrue("Authorized", authenticationContext.authorize()); return authenticationContext.getAuthorizedIdentity(); } private void checkMBeanAccess(final StandardRole standardRole, final boolean sensitiveMBeans) throws Exception { if (sensitiveMBeans) { ModelNode sensitiveMBeansOp = Util.getWriteAttributeOperation( PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, JMXExtension.SUBSYSTEM_NAME), JMXSubsystemRootResource.CORE_MBEAN_SENSITIVITY.getName(), new ModelNode(sensitiveMBeans)); executeForResult(sensitiveMBeansOp); } final boolean canRead = standardRole == null ? true : canRead(standardRole, sensitiveMBeans); final boolean canWrite = standardRole == null ? true : canWrite(standardRole, sensitiveMBeans); final boolean canAccessSpecial = standardRole == null ? true : canAccessSpecial(standardRole); try { AccessAuditContext.doAs(roleToSecurityIdentity(standardRole), null, new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { Set<ObjectInstance> instances = server.queryMBeans(null, null); Set<ObjectName> names = server.queryNames(null, null); int count = server.getMBeanCount(); Assert.assertEquals(count, names.size()); Assert.assertEquals(count, instances.size()); Assert.assertNotNull(server.getDefaultDomain()); Assert.assertTrue(server.getDomains().length > 0); //mbean count, queryMBeans/-Names, getObjectInstance(), isRegistered() if (canRead) { Assert.assertTrue(names.contains(OBJECT_NAME)); Assert.assertNotNull(server.getObjectInstance(OBJECT_NAME)); Assert.assertTrue(server.isRegistered(OBJECT_NAME)); Assert.assertNotNull(server.getMBeanInfo(OBJECT_NAME)); Assert.assertTrue(server.isInstanceOf(OBJECT_NAME,BeanMBean.class.getName())); } else { Assert.assertFalse(names.contains(OBJECT_NAME)); try { server.getObjectInstance(OBJECT_NAME); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.isRegistered(OBJECT_NAME); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.getMBeanInfo(OBJECT_NAME); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.isInstanceOf(OBJECT_NAME,BeanMBean.class.getName()); Assert.fail(); } catch (JMRuntimeException expected) { } } //Attributes if (canRead) { Assert.assertEquals(5, server.getAttribute(OBJECT_NAME, "Attr")); server.getAttributes(OBJECT_NAME, new String[] {"Attr"}); } else { try { server.getAttribute(OBJECT_NAME, "Attr"); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.getAttributes(OBJECT_NAME, new String[] {"Attr"}); Assert.fail(); } catch (JMRuntimeException expected) { } } if (canWrite) { server.setAttribute(OBJECT_NAME, new Attribute("Attr", new Integer(10))); server.setAttributes(OBJECT_NAME, new AttributeList(Collections.singletonList(new Attribute("Attr", new Integer(10))))); } else { try { server.setAttribute(OBJECT_NAME, new Attribute("Attr", new Integer(10))); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.setAttributes(OBJECT_NAME, new AttributeList(Collections.singletonList(new Attribute("Attr", new Integer(10))))); Assert.fail(); } catch (JMRuntimeException expected) { } } //createMBean, registerMBean and unregisterMBean if (canWrite) { server.createMBean(Bean.class.getName(), OBJECT_NAME2); server.unregisterMBean(OBJECT_NAME2); server.createMBean(Bean.class.getName(), OBJECT_NAME2, new Object[0], new String[0]); server.unregisterMBean(OBJECT_NAME2); server.registerMBean(new Bean(), OBJECT_NAME2); server.unregisterMBean(OBJECT_NAME2); } else { try { server.createMBean(Bean.class.getName(), OBJECT_NAME2); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.createMBean(Bean.class.getName(), OBJECT_NAME2, new Object[0], new String[0]); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.registerMBean(new Bean(), OBJECT_NAME2); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.unregisterMBean(OBJECT_NAME); Assert.fail(); } catch (JMRuntimeException expected) { } } //Notification listeners final TestNotificationListener listener = new TestNotificationListener(); if (canRead) { server.addNotificationListener(OBJECT_NAME, listener, listener, new Object()); server.removeNotificationListener(OBJECT_NAME, listener); } else { try { server.addNotificationListener(OBJECT_NAME, listener, listener, new Object()); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.removeNotificationListener(OBJECT_NAME, listener); Assert.fail(); } catch (JMRuntimeException expected) { } } //Special methods, which only superuser or administrator can call ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); try { out.writeObject(new Bean()); } finally { IoUtils.safeClose(out); } byte[] bytes = bout.toByteArray(); if (canAccessSpecial) { Assert.assertNotNull(server.deserialize(OBJECT_NAME, bytes)); Assert.assertNotNull(server.deserialize(Bean.class.getName(), bytes)); try { server.getClassLoader(OBJECT_NAME); } catch (InstanceNotFoundException expected) { } Assert.assertNotNull(server.getClassLoaderRepository()); Assert.assertNotNull(server.getClassLoaderFor(OBJECT_NAME)); Assert.assertNotNull(server.instantiate(Bean.class.getName())); Assert.assertNotNull(server.instantiate(Bean.class.getName(), new Object[0], new String[0])); } else { try { Assert.assertNotNull(server.deserialize(OBJECT_NAME, bytes)); Assert.fail(); } catch (JMRuntimeException expected) { } try { Assert.assertNotNull(server.deserialize(Bean.class.getName(), bytes)); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.getClassLoader(OBJECT_NAME); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.getClassLoaderRepository(); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.getClassLoaderFor(OBJECT_NAME); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.instantiate(Bean.class.getName()); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.instantiate(Bean.class.getName(), new Object[0], new String[0]); Assert.fail(); } catch (JMRuntimeException expected) { } } //invoke if (canRead) { server.invoke(OBJECT_NAME_MODEL, "info", new Object[0], new String[0]); } else { try { server.invoke(OBJECT_NAME_MODEL, "info", new Object[0], new String[0]); Assert.fail(); } catch (JMRuntimeException expected) { } } if (canWrite) { server.invoke(OBJECT_NAME, "method", new Object[0], new String[0]); server.invoke(OBJECT_NAME_MODEL, "action", new Object[0], new String[0]); server.invoke(OBJECT_NAME_MODEL, "actionInfo", new Object[0], new String[0]); server.invoke(OBJECT_NAME_MODEL, "unknown", new Object[0], new String[0]); } else { try { server.invoke(OBJECT_NAME, "method", new Object[0], new String[0]); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.invoke(OBJECT_NAME_MODEL, "action", new Object[0], new String[0]); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.invoke(OBJECT_NAME_MODEL, "actionInfo", new Object[0], new String[0]); Assert.fail(); } catch (JMRuntimeException expected) { } try { server.invoke(OBJECT_NAME_MODEL, "unknown", new Object[0], new String[0]); Assert.fail(); } catch (JMRuntimeException expected) { } } return null; } }); } catch (Exception e) { throw new RuntimeException(e); } } protected abstract boolean canRead(StandardRole standardRole, boolean sensitiveMBeans); protected abstract boolean canWrite(StandardRole standardRole, boolean sensitiveMBeans); protected abstract boolean canAccessSpecial(StandardRole standardRole); @Override protected DelegatingConfigurableAuthorizer getAuthorizer() { if (authorizer == null) { authorizer = new DelegatingConfigurableAuthorizer(); } return authorizer; } @Override protected ManagementSecurityIdentitySupplier getSecurityIdentitySupplier() { if (securityIdentitySupplier == null) { securityIdentitySupplier = new ManagementSecurityIdentitySupplier(); } return securityIdentitySupplier; } @Override protected void addBootOperations(List<ModelNode> bootOperations) { if (enableRbac) { for (StandardRole standardRole : EnumSet.allOf(StandardRole.class)) { ModelNode addRoleMappingOp = Util.createAddOperation( PathAddress.pathAddress( CoreManagementResourceDefinition.PATH_ELEMENT, AccessAuthorizationResourceDefinition.PATH_ELEMENT, PathElement.pathElement(RoleMappingResourceDefinition.PATH_KEY, standardRole.getFormalName()))); bootOperations.add(addRoleMappingOp); // TODO Elytron One supercalifragilisticexpialidocious hack, anonymous should mean no perms but we need to emulate the in-vm SuperUser roles. if (standardRole == StandardRole.SUPERUSER) { ModelNode addAnonymousUserOp = Util.createAddOperation( PathAddress.pathAddress( CoreManagementResourceDefinition.PATH_ELEMENT, AccessAuthorizationResourceDefinition.PATH_ELEMENT, PathElement.pathElement(RoleMappingResourceDefinition.PATH_KEY, standardRole.getFormalName()), PathElement.pathElement(ModelDescriptionConstants.INCLUDE, "anonymous"))); addAnonymousUserOp.get(PrincipalResourceDefinition.NAME.getName()).set("anonymous"); addAnonymousUserOp.get(PrincipalResourceDefinition.TYPE.getName()).set(PrincipalResourceDefinition.Type.USER.toString()); bootOperations.add(addAnonymousUserOp); } ModelNode addIncludeUserOp = Util.createAddOperation( PathAddress.pathAddress( CoreManagementResourceDefinition.PATH_ELEMENT, AccessAuthorizationResourceDefinition.PATH_ELEMENT, PathElement.pathElement(RoleMappingResourceDefinition.PATH_KEY, standardRole.getFormalName()), PathElement.pathElement(ModelDescriptionConstants.INCLUDE, "user-" + roleToUserName(standardRole)))); addIncludeUserOp.get(PrincipalResourceDefinition.NAME.getName()).set(roleToUserName(standardRole)); addIncludeUserOp.get(PrincipalResourceDefinition.TYPE.getName()).set(PrincipalResourceDefinition.Type.USER.toString()); bootOperations.add(addIncludeUserOp); } ModelNode enableRbacOp = Util.getWriteAttributeOperation( PathAddress.pathAddress( CoreManagementResourceDefinition.PATH_ELEMENT, AccessAuthorizationResourceDefinition.PATH_ELEMENT), AccessAuthorizationResourceDefinition.PROVIDER.getName(), new ModelNode(AccessAuthorizationResourceDefinition.Provider.RBAC.toString())); bootOperations.add(enableRbacOp); } ModelNode addOp = Util.createAddOperation(PathAddress.pathAddress(ModelDescriptionConstants.SUBSYSTEM, JMXExtension.SUBSYSTEM_NAME)); bootOperations.add(addOp); } private MBeanServer getMBeanServer() throws Exception { ServiceController controller = getContainer().getRequiredService(MBeanServerService.SERVICE_NAME); return (PluggableMBeanServer)controller.getValue(); } protected void initModel(ManagementModel managementModel) { ManagementResourceRegistration registration = managementModel.getRootResourceRegistration(); PathManagerService pathManagerService = new PathManagerService() { }; GlobalOperationHandlers.registerGlobalOperations(registration, processType); registration.registerOperationHandler(CompositeOperationHandler.DEFINITION, CompositeOperationHandler.INSTANCE); GlobalNotifications.registerGlobalNotifications(registration, processType); registration.registerReadOnlyAttribute(LAUNCH_TYPE, new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { context.getResult().set(TYPE_STANDALONE); } }); TestServiceListener listener = new TestServiceListener(); listener.reset(1); getContainer().addService(PathManagerService.SERVICE_NAME, pathManagerService) .addListener(listener) .install(); try { listener.latch.await(10, TimeUnit.SECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } registration.registerSubModel(PathResourceDefinition.createSpecified(pathManagerService)); registration.registerSubModel(CoreManagementResourceDefinition.forStandaloneServer(getAuthorizer(), getSecurityIdentitySupplier(), getAuditLogger(), pathManagerService, new EnvironmentNameReader() { public boolean isServer() { return true; } public String getServerName() { return "Test"; } public String getHostName() { return null; } public String getProductName() { return null; } }, null, new ResourceDefinition[0])); Resource rootResource = managementModel.getRootResource(); pathManagerService.addPathManagerResources(rootResource); ExtensionRegistry extensionRegistry = new ExtensionRegistry(ProcessType.STANDALONE_SERVER, new RunningModeControl(RunningMode.NORMAL), AuditLogger.NO_OP_LOGGER, getAuthorizer(), getSecurityIdentitySupplier(), RuntimeHostControllerInfoAccessor.SERVER); extensionRegistry.setPathManager(pathManagerService); extensionRegistry.setWriterRegistry(new NullConfigurationPersister()); JMXExtension extension = new JMXExtension(); extension.initialize(extensionRegistry.getExtensionContext("org.jboss.as.jmx", registration, ExtensionRegistryType.SLAVE)); Resource coreManagementResource = Resource.Factory.create(); rootResource.registerChild(CoreManagementResourceDefinition.PATH_ELEMENT, coreManagementResource); Resource accessAuthorizationResource = Resource.Factory.create(); accessAuthorizationResource.getModel().get(AccessAuthorizationResourceDefinition.PROVIDER.getName()).set(AccessAuthorizationResourceDefinition.Provider.SIMPLE.toString()); coreManagementResource.registerChild(AccessAuthorizationResourceDefinition.PATH_ELEMENT, accessAuthorizationResource); } private class TestServiceListener extends AbstractServiceListener<Object> { volatile CountDownLatch latch; Map<ServiceController.Transition, ServiceName> services = Collections.synchronizedMap(new LinkedHashMap<ServiceController.Transition, ServiceName>()); void reset(int count) { latch = new CountDownLatch(count); services.clear(); } public void transition(ServiceController<? extends Object> controller, ServiceController.Transition transition) { if (transition == ServiceController.Transition.STARTING_to_UP || transition == ServiceController.Transition.REMOVING_to_REMOVED) { services.put(transition, controller.getName()); latch.countDown(); } } } private static class TestNotificationListener implements NotificationListener, NotificationFilter { private static final long serialVersionUID = 1L; @Override public void handleNotification(Notification notification, Object handback) { } @Override public boolean isNotificationEnabled(Notification notification) { return false; } } }