MBeanInvocationHandlerImpl.java
001 /*
002  *
003  * Licensed to the Apache Software Foundation (ASF) under one
004  * or more contributor license agreements.  See the NOTICE file
005  * distributed with this work for additional information
006  * regarding copyright ownership.  The ASF licenses this file
007  * to you under the Apache License, Version 2.0 (the
008  * "License"); you may not use this file except in compliance
009  * with the License.  You may obtain a copy of the License at
010  *
011  *   http://www.apache.org/licenses/LICENSE-2.0
012  *
013  * Unless required by applicable law or agreed to in writing,
014  * software distributed under the License is distributed on an
015  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016  * KIND, either express or implied.  See the License for the
017  * specific language governing permissions and limitations
018  * under the License.
019  *
020  */
021 package org.apache.qpid.server.management;
022 
023 import org.apache.qpid.server.security.access.management.UserManagement;
024 import org.apache.log4j.Logger;
025 
026 import javax.management.remote.MBeanServerForwarder;
027 import javax.management.remote.JMXPrincipal;
028 import javax.management.MBeanServer;
029 import javax.management.ObjectName;
030 import javax.management.MBeanInfo;
031 import javax.management.MBeanOperationInfo;
032 import javax.management.JMException;
033 import javax.security.auth.Subject;
034 import java.lang.reflect.InvocationHandler;
035 import java.lang.reflect.Proxy;
036 import java.lang.reflect.Method;
037 import java.security.AccessController;
038 import java.security.Principal;
039 import java.security.AccessControlContext;
040 import java.util.Set;
041 import java.util.Properties;
042 
043 /**
044  * This class can be used by the JMXConnectorServer as an InvocationHandler for the mbean operations. This implements
045  * the logic for allowing the users to invoke MBean operations and implements the restrictions for readOnly, readWrite
046  * and admin users.
047  */
048 public class MBeanInvocationHandlerImpl implements InvocationHandler
049 {
050     private static final Logger _logger = Logger.getLogger(MBeanInvocationHandlerImpl.class);
051 
052     public final static String ADMIN = "admin";
053     public final static String READWRITE = "readwrite";
054     public final static String READONLY = "readonly";
055     private final static String DELEGATE = "JMImplementation:type=MBeanServerDelegate";
056     private MBeanServer mbs;
057     private static Properties _userRoles = new Properties();
058 
059     public static MBeanServerForwarder newProxyInstance()
060     {
061         final InvocationHandler handler = new MBeanInvocationHandlerImpl();
062         final Class[] interfaces = new Class[]{MBeanServerForwarder.class};
063 
064         Object proxy = Proxy.newProxyInstance(MBeanServerForwarder.class.getClassLoader(), interfaces, handler);
065         return MBeanServerForwarder.class.cast(proxy);
066     }
067 
068     public Object invoke(Object proxy, Method method, Object[] argsthrows Throwable
069     {
070         final String methodName = method.getName();
071 
072         if (methodName.equals("getMBeanServer"))
073         {
074             return mbs;
075         }
076 
077         if (methodName.equals("setMBeanServer"))
078         {
079             if (args[0== null)
080             {
081                 throw new IllegalArgumentException("Null MBeanServer");
082             }
083             if (mbs != null)
084             {
085                 throw new IllegalArgumentException("MBeanServer object already initialized");
086             }
087             mbs = (MBeanServerargs[0];
088             return null;
089         }
090 
091         // Retrieve Subject from current AccessControlContext
092         AccessControlContext acc = AccessController.getContext();
093         Subject subject = Subject.getSubject(acc);
094 
095         // Allow operations performed locally on behalf of the connector server itself
096         if (subject == null)
097         {
098             return method.invoke(mbs, args);
099         }
100 
101         if (args == null || DELEGATE.equals(args[0]))
102         {
103             return method.invoke(mbs, args);
104         }
105 
106         // Restrict access to "createMBean" and "unregisterMBean" to any user
107         if (methodName.equals("createMBean"|| methodName.equals("unregisterMBean"))
108         {
109             _logger.debug("User trying to create or unregister an MBean");
110             throw new SecurityException("Access denied");
111         }
112 
113         // Retrieve JMXPrincipal from Subject
114         Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class);
115         if (principals == null || principals.isEmpty())
116         {
117             throw new SecurityException("Access denied");
118         }
119 
120         Principal principal = principals.iterator().next();
121         String identity = principal.getName();
122 
123         if (isAdminMethod(args))
124         {
125             if (isAdmin(identity))
126             {
127                 return method.invoke(mbs, args);
128             }
129             else
130             {
131                 throw new SecurityException("Access denied");
132             }
133         }
134 
135         // Following users can perform any operation other than "createMBean" and "unregisterMBean"
136         if (isAllowedToModify(identity))
137         {
138             return method.invoke(mbs, args);
139         }
140 
141         // These users can only call "getAttribute" on the MBeanServerDelegate MBean
142         // Here we can add other fine grained permissions like specific method for a particular mbean
143         if (isReadOnlyUser(identity&& isReadOnlyMethod(method, args))
144         {
145             return method.invoke(mbs, args);
146         }
147 
148         throw new SecurityException("Access denied");
149     }
150 
151     private boolean isAdminMethod(Object[] args)
152     {
153         if (args[0instanceof ObjectName)
154         {
155             ObjectName object = (ObjectNameargs[0];
156             return UserManagement.TYPE.equals(object.getKeyProperty("type"));
157         }
158 
159         return false;
160     }
161 
162     // Initialises the user roles
163     public static void setAccessRights(Properties accessRights)
164     {
165         _userRoles = accessRights;
166     }
167 
168     private boolean isAdmin(String userName)
169     {
170         if (ADMIN.equals(_userRoles.getProperty(userName)))
171         {
172             return true;
173         }
174         return false;
175     }
176 
177     private boolean isAllowedToModify(String userName)
178     {
179         if (ADMIN.equals(_userRoles.getProperty(userName))
180             || READWRITE.equals(_userRoles.getProperty(userName)))
181         {
182             return true;
183         }
184         return false;
185     }
186 
187     private boolean isReadOnlyUser(String userName)
188     {
189         if (READONLY.equals(_userRoles.getProperty(userName)))
190         {
191             return true;
192         }
193         return false;
194     }
195 
196     private boolean isReadOnlyMethod(Method method, Object[] args)
197     {
198         String methodName = method.getName();
199         if (methodName.startsWith("query"|| methodName.startsWith("get"))
200         {
201             return true;
202         }
203         else if (methodName.startsWith("set"))
204         {
205             return false;
206         }
207 
208         if ((args[0instanceof ObjectName&& (methodName.equals("invoke")))
209         {
210             String mbeanMethod = (args.length > 1(Stringargs[1null;
211             if (mbeanMethod == null)
212             {
213                 return false;
214             }
215 
216             try
217             {
218                 MBeanInfo mbeanInfo = mbs.getMBeanInfo((ObjectNameargs[0]);
219                 if (mbeanInfo != null)
220                 {
221                     MBeanOperationInfo[] opInfos = mbeanInfo.getOperations();
222                     for (MBeanOperationInfo opInfo : opInfos)
223                     {
224                         if (opInfo.getName().equals(mbeanMethod&& (opInfo.getImpact() == MBeanOperationInfo.INFO))
225                         {
226                             return true;
227                         }
228                     }
229                 }
230             }
231             catch (JMException ex)
232             {
233                 ex.printStackTrace();
234             }
235         }
236 
237         return false;
238     }
239 }