AMQUserManagementMBean.java
001 /*
002  *  Licensed to the Apache Software Foundation (ASF) under one
003  *  or more contributor license agreements.  See the NOTICE file
004  *  distributed with this work for additional information
005  *  regarding copyright ownership.  The ASF licenses this file
006  *  to you under the Apache License, Version 2.0 (the
007  *  "License"); you may not use this file except in compliance
008  *  with the License.  You may obtain a copy of the License at
009  *
010  *    http://www.apache.org/licenses/LICENSE-2.0
011  *
012  *  Unless required by applicable law or agreed to in writing,
013  *  software distributed under the License is distributed on an
014  *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015  *  KIND, either express or implied.  See the License for the
016  *  specific language governing permissions and limitations
017  *  under the License.    
018  *
019  
020  */
021 package org.apache.qpid.server.security.access.management;
022 
023 import org.apache.qpid.server.management.MBeanDescription;
024 import org.apache.qpid.server.management.AMQManagedObject;
025 import org.apache.qpid.server.management.MBeanOperation;
026 import org.apache.qpid.server.management.MBeanInvocationHandlerImpl;
027 import org.apache.qpid.server.security.auth.database.PrincipalDatabase;
028 import org.apache.qpid.server.security.auth.sasl.UsernamePrincipal;
029 import org.apache.qpid.server.security.access.management.UserManagement;
030 import org.apache.log4j.Logger;
031 import org.apache.commons.configuration.ConfigurationException;
032 
033 import javax.management.JMException;
034 import javax.management.remote.JMXPrincipal;
035 import javax.management.openmbean.TabularData;
036 import javax.management.openmbean.TabularDataSupport;
037 import javax.management.openmbean.TabularType;
038 import javax.management.openmbean.SimpleType;
039 import javax.management.openmbean.CompositeType;
040 import javax.management.openmbean.OpenType;
041 import javax.management.openmbean.OpenDataException;
042 import javax.management.openmbean.CompositeData;
043 import javax.management.openmbean.CompositeDataSupport;
044 import javax.security.auth.login.AccountNotFoundException;
045 import javax.security.auth.Subject;
046 import java.io.File;
047 import java.io.FileInputStream;
048 import java.io.IOException;
049 import java.io.FileOutputStream;
050 import java.util.Properties;
051 import java.util.List;
052 import java.util.Enumeration;
053 import java.util.Set;
054 import java.util.concurrent.locks.ReentrantLock;
055 import java.security.Principal;
056 import java.security.AccessControlContext;
057 import java.security.AccessController;
058 
059 /** MBean class for AMQUserManagementMBean. It implements all the management features exposed for managing users. */
060 @MBeanDescription("User Management Interface")
061 public class AMQUserManagementMBean extends AMQManagedObject implements UserManagement
062 {
063 
064     private static final Logger _logger = Logger.getLogger(AMQUserManagementMBean.class);
065 
066     private PrincipalDatabase _principalDatabase;
067     private String _accessFileName;
068     private Properties _accessRights;
069     //    private File _accessFile;
070     private ReentrantLock _accessRightsUpdate = new ReentrantLock();
071 
072     // Setup for the TabularType
073     static TabularType _userlistDataType; // Datatype for representing User Lists
074 
075     static CompositeType _userDataType; // Composite type for representing User
076     static String[] _userItemNames = {"Username""read""write""admin"};
077 
078     static
079     {
080         String[] userItemDesc = {"Broker Login username""Management Console Read Permission",
081                                  "Management Console Write Permission""Management Console Admin Permission"};
082 
083         OpenType[] userItemTypes = new OpenType[4]// User item types.
084         userItemTypes[0= SimpleType.STRING;  // For Username
085         userItemTypes[1= SimpleType.BOOLEAN; // For Rights - Read
086         userItemTypes[2= SimpleType.BOOLEAN; // For Rights - Write
087         userItemTypes[3= SimpleType.BOOLEAN; // For Rights - Admin
088         String[] userDataIndex = {_userItemNames[0]};
089 
090         try
091         {
092             _userDataType =
093                     new CompositeType("User""User Data", _userItemNames, userItemDesc, userItemTypes);
094 
095             _userlistDataType = new TabularType("Users""List of users", _userDataType, userDataIndex);
096         }
097         catch (OpenDataException e)
098         {
099             _logger.error("Tabular data setup for viewing users incorrect.");
100             _userlistDataType = null;
101         }
102     }
103 
104 
105     public AMQUserManagementMBean() throws JMException
106     {
107         super(UserManagement.class, UserManagement.TYPE);
108     }
109 
110     public String getObjectInstanceName()
111     {
112         return UserManagement.TYPE;
113     }
114 
115     public boolean setPassword(String username, char[] password)
116     {
117         try
118         {
119             //delegate password changes to the Principal Database
120             return _principalDatabase.updatePassword(new UsernamePrincipal(username), password);
121         }
122         catch (AccountNotFoundException e)
123         {
124             _logger.warn("Attempt to set password of non-existant user'" + username + "'");
125             return false;
126         }
127     }
128 
129     public boolean setRights(String username, boolean read, boolean write, boolean admin)
130     {
131 
132         if (_accessRights.get(username== null)
133         {
134             // If the user doesn't exist in the user rights file check that they at least have an account.
135             if (_principalDatabase.getUser(username== null)
136             {
137                 return false;
138             }
139         }
140 
141         try
142         {
143 
144             _accessRightsUpdate.lock();
145 
146             // Update the access rights
147             if (admin)
148             {
149                 _accessRights.put(username, MBeanInvocationHandlerImpl.ADMIN);
150             }
151             else
152             {
153                 if (read | write)
154                 {
155                     if (read)
156                     {
157                         _accessRights.put(username, MBeanInvocationHandlerImpl.READONLY);
158                     }
159                     if (write)
160                     {
161                         _accessRights.put(username, MBeanInvocationHandlerImpl.READWRITE);
162                     }
163                 }
164                 else
165                 {
166                     _accessRights.remove(username);
167                 }
168             }
169 
170             saveAccessFile();
171         }
172         finally
173         {
174             if (_accessRightsUpdate.isHeldByCurrentThread())
175             {
176                 _accessRightsUpdate.unlock();
177             }
178         }
179 
180         return true;
181     }
182 
183     public boolean createUser(String username, char[] password, boolean read, boolean write, boolean admin)
184     {
185         if (_principalDatabase.createPrincipal(new UsernamePrincipal(username), password))
186         {
187             _accessRights.put(username, "");
188 
189             return setRights(username, read, write, admin);
190         }
191 
192         return false;
193     }
194 
195     public boolean deleteUser(String username)
196     {
197 
198         try
199         {
200             if (_principalDatabase.deletePrincipal(new UsernamePrincipal(username)))
201             {
202                 try
203                 {
204                     _accessRightsUpdate.lock();
205 
206                     _accessRights.remove(username);
207                     saveAccessFile();
208                 }
209                 finally
210                 {
211                     if (_accessRightsUpdate.isHeldByCurrentThread())
212                     {
213                         _accessRightsUpdate.unlock();
214                     }
215                 }
216                 return true;
217             }
218         }
219         catch (AccountNotFoundException e)
220         {
221             _logger.warn("Attempt to delete user (" + username + ") that doesn't exist");
222         }
223 
224         return false;
225     }
226 
227     public boolean reloadData()
228     {
229             try
230             {
231                 loadAccessFile();
232                 _principalDatabase.reload();
233             }
234             catch (ConfigurationException e)
235             {
236                 _logger.info("Reload failed due to:" + e);
237                 return false;
238             }
239             catch (IOException e)
240             {
241                 _logger.info("Reload failed due to:" + e);
242                 return false;
243             }
244             // Reload successful
245             return true;
246     }
247 
248 
249     @MBeanOperation(name = "viewUsers", description = "All users with access rights to the system.")
250     public TabularData viewUsers()
251     {
252         // Table of users
253         // Username(string), Access rights Read,Write,Admin(bool,bool,bool)
254 
255         if (_userlistDataType == null)
256         {
257             _logger.warn("TabluarData not setup correctly");
258             return null;
259         }
260 
261         List<Principal> users = _principalDatabase.getUsers();
262 
263         TabularDataSupport userList = new TabularDataSupport(_userlistDataType);
264 
265         try
266         {
267             // Create the tabular list of message header contents
268             for (Principal user : users)
269             {
270                 // Create header attributes list
271 
272                 String rights = (String_accessRights.get(user.getName());
273 
274                 Boolean read = false;
275                 Boolean write = false;
276                 Boolean admin = false;
277 
278                 if (rights != null)
279                 {
280                     read = rights.equals(MBeanInvocationHandlerImpl.READONLY)
281                            || rights.equals(MBeanInvocationHandlerImpl.READWRITE);
282                     write = rights.equals(MBeanInvocationHandlerImpl.READWRITE);
283                     admin = rights.equals(MBeanInvocationHandlerImpl.ADMIN);
284                 }
285 
286                 Object[] itemData = {user.getName(), read, write, admin};
287                 CompositeData messageData = new CompositeDataSupport(_userDataType, _userItemNames, itemData);
288                 userList.put(messageData);
289             }
290         }
291         catch (OpenDataException e)
292         {
293             _logger.warn("Unable to create user list due to :" + e);
294             return null;
295         }
296 
297         return userList;
298     }
299 
300     /*** Broker Methods **/
301 
302     /**
303      * setPrincipalDatabase
304      *
305      @param database set The Database to use for user lookup
306      */
307     public void setPrincipalDatabase(PrincipalDatabase database)
308     {
309         _principalDatabase = database;
310     }
311 
312     /**
313      * setAccessFile
314      *
315      @param accessFile the file to use for updating.
316      *
317      @throws java.io.IOException If the file cannot be accessed
318      @throws org.apache.commons.configuration.ConfigurationException
319      *                             if checks on the file fail.
320      */
321     public void setAccessFile(String accessFilethrows IOException, ConfigurationException
322     {
323         _accessFileName = accessFile;
324 
325         if (_accessFileName != null)
326         {
327             loadAccessFile();
328         }
329         else
330         {
331             _logger.warn("Access rights file specified is null. Access rights not changed.");
332         }
333     }
334 
335     private void loadAccessFile() throws IOException, ConfigurationException
336     {
337         try
338         {
339             _accessRightsUpdate.lock();
340 
341             Properties accessRights = new Properties();
342 
343             File accessFile = new File(_accessFileName);
344 
345             if (!accessFile.exists())
346             {
347                 throw new ConfigurationException("'" + _accessFileName + "' does not exist");
348             }
349 
350             if (!accessFile.canRead())
351             {
352                 throw new ConfigurationException("Cannot read '" + _accessFileName + "'.");
353             }
354 
355             if (!accessFile.canWrite())
356             {
357                 _logger.warn("Unable to write to access file '" + _accessFileName + "' changes will not be preserved.");
358             }
359 
360             accessRights.load(new FileInputStream(accessFile));
361             checkAccessRights(accessRights);
362             setAccessRights(accessRights);
363         }
364         finally
365         {
366             if (_accessRightsUpdate.isHeldByCurrentThread())
367             {
368                 _accessRightsUpdate.unlock();
369             }
370         }
371     }
372 
373     private void checkAccessRights(Properties accessRights)
374     {
375         Enumeration values = accessRights.propertyNames();
376 
377         while (values.hasMoreElements())
378         {
379             String user = (Stringvalues.nextElement();
380 
381             if (_principalDatabase.getUser(user== null)
382             {
383                 _logger.warn("Access rights contains user '" + user + "' but there is no authentication data for that user");
384             }
385         }
386     }
387 
388     private void saveAccessFile()
389     {
390         try
391         {
392             _accessRightsUpdate.lock();
393             try
394             {
395                 // Create temporary file
396                 File tmp = File.createTempFile(_accessFileName, ".tmp");
397 
398                 // Rename current file
399                 File rights = new File(_accessFileName);
400 
401                 FileOutputStream output = new FileOutputStream(tmp);
402                 _accessRights.store(output, "Generated by AMQUserManagementMBean Console : Last edited by user:" + getCurrentJMXUser());
403                 output.close();
404 
405                 // Rename new file to main file
406                 tmp.renameTo(rights);
407 
408                 // delete tmp
409                 tmp.delete();
410             }
411             catch (IOException e)
412             {
413                 _logger.warn("Problem occured saving '" + _accessFileName + "' changes may not be preserved. :" + e);
414             }
415         }
416         finally
417         {
418             if (_accessRightsUpdate.isHeldByCurrentThread())
419             {
420                 _accessRightsUpdate.unlock();
421             }
422         }
423     }
424 
425     private String getCurrentJMXUser()
426     {
427         AccessControlContext acc = AccessController.getContext();
428         
429         Subject subject = Subject.getSubject(acc);
430         if (subject == null)
431         {
432             return "Unknown user, authentication Subject was null";
433         }
434 
435         // Retrieve JMXPrincipal from Subject
436         Set<JMXPrincipal> principals = subject.getPrincipals(JMXPrincipal.class);
437         if (principals == null || principals.isEmpty())
438         {
439             return "Unknown user principals were null";
440         }
441 
442         Principal principal = principals.iterator().next();
443         return principal.getName();
444     }
445 
446     /**
447      * user=read user=write user=readwrite user=admin
448      *
449      @param accessRights The properties list of access rights to process
450      */
451     private void setAccessRights(Properties accessRights)
452     {
453         _logger.debug("Setting Access Rights:" + accessRights);
454         _accessRights = accessRights;
455         MBeanInvocationHandlerImpl.setAccessRights(_accessRights);
456     }
457 }