1/*
2 * Copyright (c) 2017, 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 6515161
27 * @summary checks the behaviour of  mbeanServerConnection.removeNotificationListener
28 * operation when there is a exception thrown during removal
29 * @modules java.management
30 * @run main NoPermToRemoveTest
31 */
32
33import java.lang.management.ManagementFactory;
34import java.security.AllPermission;
35import java.security.CodeSource;
36import java.security.Permission;
37import java.security.PermissionCollection;
38import java.security.Permissions;
39import java.security.Policy;
40import java.security.ProtectionDomain;
41import java.util.concurrent.Semaphore;
42import java.util.concurrent.TimeUnit;
43import java.util.concurrent.atomic.AtomicInteger;
44import javax.management.ListenerNotFoundException;
45import javax.management.MBeanPermission;
46import javax.management.MBeanServer;
47import javax.management.MBeanServerConnection;
48import javax.management.Notification;
49import javax.management.NotificationBroadcasterSupport;
50import javax.management.NotificationFilter;
51import javax.management.NotificationListener;
52import javax.management.ObjectName;
53import javax.management.remote.JMXConnector;
54import javax.management.remote.JMXConnectorFactory;
55import javax.management.remote.JMXConnectorServer;
56import javax.management.remote.JMXConnectorServerFactory;
57import javax.management.remote.JMXServiceURL;
58
59public class NoPermToRemoveTest {
60    public static void main(String[] args) throws Exception {
61        Policy.setPolicy(new NoRemovePolicy());
62        System.setSecurityManager(new SecurityManager());
63
64        JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///");
65        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
66        ObjectName name = new ObjectName("foo:type=Sender");
67        mbs.registerMBean(new Sender(), name);
68        JMXConnectorServer cs = JMXConnectorServerFactory.newJMXConnectorServer(
69                url, null, mbs);
70        cs.start();
71        try {
72            JMXServiceURL addr = cs.getAddress();
73            JMXConnector cc = JMXConnectorFactory.connect(addr);
74            MBeanServerConnection mbsc = cc.getMBeanServerConnection();
75            SnoopListener listener = new SnoopListener();
76            mbsc.addNotificationListener(name, listener, null, null);
77            mbsc.invoke(name, "send", null, null);
78            if (!listener.waitForNotification(60))
79                throw new Exception("Did not receive expected notification");
80
81            try {
82                mbsc.removeNotificationListener(name, listener);
83                throw new Exception("RemoveNL did not get SecurityException");
84            } catch (SecurityException e) {
85                System.out.println("removeNL got expected exception: " + e);
86            }
87            mbsc.invoke(name, "send", null, null);
88            if (!listener.waitForNotification(60)) {
89                int listenerCount =
90                        (Integer) mbsc.getAttribute(name, "ListenerCount");
91                System.out.println("Listener count: " + listenerCount);
92                if (listenerCount != 0)
93                    throw new Exception("TEST FAILED");
94                    /* We did not receive the notification, but the MBean still
95                     * has a listener coming from the connector server, which
96                     * means the connector server still thinks there is a
97                     * listener.  If we retained the listener after the failing
98                     * removeNL that would be OK, and if the listener were
99                     * dropped by both client and server that would be OK too,
100                     * but the inconsistency is not OK.
101                     */
102            }
103            cc.close();
104        } finally {
105            cs.stop();
106        }
107    }
108
109    private static class SnoopListener implements NotificationListener {
110        private Semaphore sema = new Semaphore(0);
111
112        public void handleNotification(Notification notification, Object handback) {
113            System.out.println("Listener got: " + notification);
114            sema.release();
115        }
116
117        boolean waitForNotification(int seconds) throws InterruptedException {
118            return sema.tryAcquire(seconds, TimeUnit.SECONDS);
119        }
120    }
121
122    private static class NoRemovePolicy extends Policy {
123        public PermissionCollection getPermissions(CodeSource codesource) {
124            PermissionCollection pc = new Permissions();
125            pc.add(new AllPermission());
126            return pc;
127        }
128
129        public void refresh() {
130        }
131
132        public boolean implies(ProtectionDomain domain, Permission permission) {
133            if (!(permission instanceof MBeanPermission))
134                return true;
135            MBeanPermission jmxp = (MBeanPermission) permission;
136            if (jmxp.getActions().contains("removeNotificationListener")) {
137                System.out.println("DENIED");
138                return false;
139            }
140            return true;
141        }
142    }
143
144    public static interface SenderMBean {
145        public void send();
146        public int getListenerCount();
147    }
148
149    public static class Sender extends NotificationBroadcasterSupport
150            implements SenderMBean {
151        private AtomicInteger listenerCount = new AtomicInteger();
152
153        public void send() {
154            System.out.println("Sending notif");
155            sendNotification(new Notification("type", this, 0L));
156        }
157
158        public synchronized int getListenerCount() {
159            return listenerCount.get();
160        }
161
162        public void removeNotificationListener(
163                NotificationListener listener,
164                NotificationFilter filter,
165                Object handback) throws ListenerNotFoundException {
166            System.out.println("Sender.removeNL(3)");
167            super.removeNotificationListener(listener, filter, handback);
168            listenerCount.decrementAndGet();
169        }
170
171        public void addNotificationListener(
172                NotificationListener listener,
173                NotificationFilter filter,
174                Object handback) {
175            System.out.println("Sender.addNL(3)");
176            super.addNotificationListener(listener, filter, handback);
177            listenerCount.incrementAndGet();
178        }
179
180        public void removeNotificationListener(NotificationListener listener)
181        throws ListenerNotFoundException {
182            System.out.println("Sender.removeNL(1)");
183            super.removeNotificationListener(listener);
184            listenerCount.decrementAndGet();
185        }
186    }
187}
188