View Javadoc

1   /**
2    * Copyright 2009 OPS4J
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.ops4j.pax.useradmin.service.internal;
19  
20  import java.security.NoSuchAlgorithmException;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Dictionary;
24  import java.util.Hashtable;
25  import java.util.Map;
26  
27  import org.ops4j.pax.useradmin.service.UserAdminConstants;
28  import org.ops4j.pax.useradmin.service.UserAdminTools;
29  import org.ops4j.pax.useradmin.service.spi.StorageException;
30  import org.ops4j.pax.useradmin.service.spi.StorageProvider;
31  import org.ops4j.pax.useradmin.service.spi.UserAdminFactory;
32  import org.osgi.framework.BundleContext;
33  import org.osgi.framework.Constants;
34  import org.osgi.framework.InvalidSyntaxException;
35  import org.osgi.framework.ServiceReference;
36  import org.osgi.service.cm.ConfigurationException;
37  import org.osgi.service.cm.ManagedService;
38  import org.osgi.service.event.Event;
39  import org.osgi.service.event.EventAdmin;
40  import org.osgi.service.log.LogService;
41  import org.osgi.service.useradmin.Authorization;
42  import org.osgi.service.useradmin.Group;
43  import org.osgi.service.useradmin.Role;
44  import org.osgi.service.useradmin.User;
45  import org.osgi.service.useradmin.UserAdmin;
46  import org.osgi.service.useradmin.UserAdminEvent;
47  import org.osgi.service.useradmin.UserAdminListener;
48  import org.osgi.service.useradmin.UserAdminPermission;
49  import org.osgi.util.tracker.ServiceTracker;
50  
51  /**
52   * An implementation of the OSGi UserAdmin service. Allows to administer user
53   * and group/role data using pluggable storage providers that connect to an
54   * underlying datastore.
55   * 
56   * @see http://www.osgi.org/javadoc/r4v42/org/osgi/service/useradmin/UserAdmin.html
57   * 
58   * @author Matthias Kuespert
59   * @since 02.07.2009
60   */
61  public class UserAdminImpl implements UserAdmin, ManagedService, UserAdminUtil, UserAdminFactory {
62  
63      private BundleContext       m_context         = null;
64  
65      private UserAdminPermission m_adminPermission = null;
66  
67      /**
68       * The ServiceTracker which monitors the service used to store data.
69       */
70      private ServiceTracker      m_storageService  = null;
71  
72      /**
73       * The ServiceTracker which monitors the service used for logging.
74       */
75      private ServiceTracker      m_logService      = null;
76  
77      /**
78       * The ServiceTracker which monitors the service used for firing events.
79       */
80      private ServiceTracker      m_eventService    = null;
81  
82      /**
83       * The ServiceTracker which tracks the registered UserAdminListeners.
84       */
85      private ServiceTracker      m_eventListeners  = null;
86  
87      /**
88       * The encryptor that is used for encrypting sensible data (e.g. user credentials).
89       */
90      private EncryptorImpl       m_encryptor       = null;
91  
92      /**
93       * Constructor - creates and initializes a <code>UserAdminImpl</code> instance.
94       * 
95       * @param context The <code>BundleContext</code>
96       * @param storageService A <code>ServiceTracker</code> to locate the <code>StorageProvider</code> service to use.
97       * @param logService A <code>ServiceTracker</code> to locate the <code>LogService</code> to use.
98       * @param eventService A <code>ServiceTracker</code> to locate the <code>EventAdmin</code> service to use.
99       */
100     protected UserAdminImpl(BundleContext context,
101                             ServiceTracker storageService,
102                             ServiceTracker logService,
103                             ServiceTracker eventService) {
104         if (null == storageService) {
105             throw new IllegalArgumentException("No StorageProvider ServiceTracker specified.");
106         }
107         if (null == logService) {
108             throw new IllegalArgumentException("No LogService ServiceTracker specified.");
109         }
110         if (null == eventService) {
111             throw new IllegalArgumentException("No EventAdmin ServiceTracker specified.");
112         }
113         m_storageService = storageService;
114         m_storageService.open();
115         m_logService = logService;
116         m_logService.open();
117         m_eventService = eventService;
118         m_eventService.open();
119         m_context = context;
120         //
121         m_eventListeners = new ServiceTracker(context, UserAdminListener.class.getName(), null);
122         m_eventListeners.open();
123         //
124         m_encryptor = null;
125     }
126 
127     /**
128      * Maps event codes to strings.
129      * 
130      * @param type The type of the event
131      * @return The event code as string
132      */
133     private String getEventTypeName(int type) {
134         String typeName = null;
135         switch (type) {
136             case UserAdminEvent.ROLE_CHANGED:
137                 typeName = "ROLE_CHANGED";
138                 break;
139             case UserAdminEvent.ROLE_CREATED:
140                 typeName = "ROLE_CREATED";
141                 break;
142             case UserAdminEvent.ROLE_REMOVED:
143                 typeName = "ROLE_REMOVED";
144             default:
145                 typeName = "Event" + type;
146         }
147         return typeName;
148     }
149 
150     /**
151      * Checks if the caller has admin permissions when security is enabled. If
152      * security is not enabled nothing happens here.
153      * 
154      * @throws <code>SecurityException</code> If security is enabled, a security
155      *         manager exists and the caller does not have the
156      *         UserAdminPermission with name admin.
157      */
158     protected void checkAdminPermission() {
159         SecurityManager sm = System.getSecurityManager();
160         if (null != sm) {
161             if (null == m_adminPermission) {
162                 m_adminPermission = new UserAdminPermission(UserAdminPermission.ADMIN, null);
163             }
164             sm.checkPermission(m_adminPermission);
165         }
166     }
167     
168     /**
169      * Creates an appropriate encryptor.
170      * 
171      * @param encryptionAlgorithm The encryption algorithm to use.
172      * @param encryptionRandomAlgorithm The random number algorithm to use.
173      * @param encryptionRandomAlgorithmSaltLength The klength of the salt to use for random number generation.
174      * @return An implementation of the encryptor.
175      * 
176      * @throws ConfigurationException
177      */
178     private EncryptorImpl createEncryptor(String encryptionAlgorithm,
179                                           String encryptionRandomAlgorithm,
180                                           String encryptionRandomAlgorithmSaltLength) throws ConfigurationException {
181         EncryptorImpl encryptor = null;
182         try {
183             encryptor = new EncryptorImpl(encryptionAlgorithm,
184                                             encryptionRandomAlgorithm,
185                                             encryptionRandomAlgorithmSaltLength);
186         } catch (NoSuchAlgorithmException e) {
187             throw new ConfigurationException(UserAdminConstants.PROP_ENCRYPTION_ALGORITHM
188                                                + " or " + UserAdminConstants.PROP_ENCRYPTION_RANDOM_ALGORITHM,
189                                              "Encryption algorithm not supported: " + e.getMessage(), e);
190         }
191         return encryptor;
192     }
193 
194     // ManagedService interface
195     
196     /**
197      * Copies all properties to an internal Map.
198      * 
199      * @see ManagedService#updated(Dictionary)
200      */
201     @SuppressWarnings(value = "unchecked")
202     public void updated(Dictionary properties) throws ConfigurationException {
203         if (null == properties) {
204             // ignore empty properties
205             return;
206         }
207         String encryptionAlgorithm = UserAdminTools.getOptionalProperty(properties,
208                                                                         UserAdminConstants.PROP_ENCRYPTION_ALGORITHM,
209                                                                         UserAdminConstants.ENCRYPTION_ALGORITHM_NONE);
210         if (UserAdminConstants.ENCRYPTION_ALGORITHM_NONE.equals(encryptionAlgorithm)) {
211             // set no encryption
212             m_encryptor = null;
213         }
214         else {
215             // create encryptor ...
216             String encryptionRandomAlgorithm = UserAdminTools.getOptionalProperty(properties,
217                                                                                   UserAdminConstants.PROP_ENCRYPTION_RANDOM_ALGORITHM,
218                                                                                   UserAdminConstants.DEFAULT_ENCRYPTION_RANDOM_ALGORITHM);
219             String encryptionRandomAlgorithmSaltLength = UserAdminTools.getOptionalProperty(properties,
220                                                                                             UserAdminConstants.PROP_ENCRYPTION_RANDOM_SALTLENGTH,
221                                                                                             UserAdminConstants.DEFAULT_ENCRYPTION_RANDOM_SALTLENGTH);
222             m_encryptor = createEncryptor(encryptionAlgorithm,
223                                           encryptionRandomAlgorithm,
224                                           encryptionRandomAlgorithmSaltLength);
225         }
226     }
227     
228     // UserAdmin interface
229 
230     /**
231      * @see UserAdmin#createRole(String, int)
232      */
233     public Role createRole(String name, int type) {
234         if (null == name) {
235             throw new IllegalArgumentException(UserAdminMessages.MSG_INVALID_NAME);
236         }
237         if (   (type != Role.GROUP)
238             && (type != Role.USER)) {
239             throw new IllegalArgumentException(UserAdminMessages.MSG_INVALID_ROLE_TYPE);
240         }
241         if (null != getRole(name)) {
242             logMessage(this, LogService.LOG_WARNING, "role already exists: " + name);
243             return null;
244         }
245         checkAdminPermission();
246         //
247         Role role = null;
248         try {
249             StorageProvider storageProvider = getStorageProvider();
250             switch (type) {
251                 case Role.USER:
252                     role = storageProvider.createUser(this, name);
253                     break;
254 
255                 case Role.GROUP:
256                     role = storageProvider.createGroup(this, name);
257                     break;
258 
259                 default:
260                     // never reached b/o previous checks
261                     break;
262             }
263             fireEvent(UserAdminEvent.ROLE_CREATED, role);
264         } catch (StorageException e) {
265             logMessage(this, LogService.LOG_ERROR, e.getMessage());
266         }
267         return role;
268     }
269 
270     /**
271      * @see UserAdmin#getAuthorization(User)
272      */
273     public Authorization getAuthorization(User user) {
274         if (null == user) {
275             throw (new IllegalArgumentException(UserAdminMessages.MSG_INVALID_USER));
276         }
277         AuthorizationImpl authorization = new AuthorizationImpl(this, user);
278         return authorization;
279     }
280 
281     /**
282      * @see UserAdmin#getRole(String)
283      */
284     public Role getRole(String name) {
285         if (null == name) {
286             throw (new IllegalArgumentException(UserAdminMessages.MSG_INVALID_NAME));
287         }
288         if ("".equals(name)) {
289             name = Role.USER_ANYONE;
290         }
291         //
292         try {
293             StorageProvider storage = getStorageProvider();
294             return storage.getRole(this, name);
295         } catch (StorageException e) {
296             logMessage(this, LogService.LOG_ERROR, e.getMessage());
297         }
298         return null;
299     }
300 
301     /**
302      * @see UserAdmin#getRoles(String)
303      */
304     public Role[] getRoles(String filter) throws InvalidSyntaxException {
305         try {
306             StorageProvider storage = getStorageProvider();
307             Collection<Role> roles = storage.findRoles(this, filter);
308             if (!roles.isEmpty()) {
309                 return roles.toArray(new Role[0]);
310             }
311         } catch (StorageException e) {
312             logMessage(this, LogService.LOG_ERROR, e.getMessage());
313         }
314         return null;
315     }
316 
317     /**
318      * @see UserAdmin#getUser(String, String)
319      */
320     public User getUser(String key, String value) {
321         if (null == key) {
322             throw new IllegalArgumentException(UserAdminMessages.MSG_INVALID_KEY);
323         }
324         if (null == value) {
325             throw new IllegalArgumentException(UserAdminMessages.MSG_INVALID_VALUE);
326         }
327         try {
328             StorageProvider storage = getStorageProvider();
329             User user = storage.getUser(this, key, value);
330             return user;
331         } catch (StorageException e) {
332             logMessage(this, LogService.LOG_ERROR, e.getMessage());
333         }
334         return null;
335     }
336 
337     /**
338      * @see UserAdmin#removeRole(String)
339      */
340     public boolean removeRole(String name) {
341         if (null == name) {
342             throw (new IllegalArgumentException(UserAdminMessages.MSG_INVALID_NAME));
343         }
344         if (!"".equals(name) && !Role.USER_ANYONE.equals(name)) {
345             Role role = getRole(name);
346             if (null != role) {
347                 checkAdminPermission();
348                 try {
349                     StorageProvider storageProvider = getStorageProvider();
350                     if (storageProvider.deleteRole(role)) {
351                         fireEvent(UserAdminEvent.ROLE_REMOVED, role);
352                         return true;
353                     } else {
354                         logMessage(this, LogService.LOG_ERROR, "Role '" + name + "' could not be deleted");
355                     }
356                 } catch (StorageException e) {
357                     logMessage(this, LogService.LOG_ERROR, e.getMessage());
358                 }
359             } else {
360                 logMessage(this, LogService.LOG_ERROR, "Role '" + name + "' does not exist.");
361             }
362         } else {
363             logMessage(this, LogService.LOG_ERROR, "Standard user '" + Role.USER_ANYONE + "' cannot be removed.");
364         }
365         return false;
366     }
367 
368     // UserAdminUtil interface
369 
370     /**
371      * @see UserAdminUtil#getStorageProvider()
372      */
373     public StorageProvider getStorageProvider() throws StorageException {
374         StorageProvider storageProvider = (StorageProvider) m_storageService.getService();
375         if (null == storageProvider) {
376             throw new StorageException(UserAdminMessages.MSG_MISSING_STORAGE_SERVICE);
377         }
378         return storageProvider;
379     }
380 
381     /**
382      * @see UserAdminUtil#logMessage(Object, int, String)
383      * 
384      * TODO: do we need a check for valid levels? What to do then: exception or ignore?
385      */
386     public void logMessage(Object source, int level, String message) {
387         LogService log = (LogService) m_logService.getService();
388         if (null != log) {
389             log.log(level, "[" + (source != null ? source.getClass().getName() : "none") + "] " + message);
390         }
391     }
392 
393     /**
394      * @see UserAdminUtil#fireEvent(int, Role)
395      */
396     public void fireEvent(int type, Role role) {
397         if (null == role) {
398             throw new IllegalArgumentException("parameter role must not be null");
399         }
400         ServiceReference ref = m_context.getServiceReference(UserAdmin.class.getName());
401         final UserAdminEvent uaEvent = new UserAdminEvent(ref, type, role);
402         //
403         // send event to all listeners, asynchronously - in a separate thread!!
404         // 
405         Object[] eventListeners = m_eventListeners.getServices();
406         if (null != eventListeners) {
407             for (Object listenerObject : eventListeners) {
408                 final UserAdminListener listener = (UserAdminListener) listenerObject;
409                 Thread notifyThread = new Thread() {
410                     @Override
411                     public void run() {
412                         listener.roleChanged(uaEvent);
413                     }
414                 };
415                 notifyThread.start();
416             }
417         }
418         //
419         // send event to EventAdmin if present
420         //
421         EventAdmin eventAdmin = (EventAdmin) m_eventService.getService();
422         if (null != eventAdmin) {
423             Dictionary<String, Object> properties = new Hashtable<String, Object>();
424             properties.put("event", uaEvent);
425             properties.put("role", role);
426             properties.put("role.name", role.getName());
427             properties.put("role.type", role.getType());
428             properties.put("service", ref);
429             properties.put("service.id", ref.getProperty(Constants.SERVICE_ID));
430             properties.put("service.objectClass", ref.getProperty(Constants.OBJECTCLASS));
431             properties.put("service.pid", ref.getProperty(Constants.SERVICE_PID));
432             //
433             Event event = new Event(UserAdminConstants.EVENT_TOPIC_PREFIX + getEventTypeName(type), properties);
434             eventAdmin.postEvent(event);
435         } else {
436             String message =   "No event service available - cannot send event of type '"
437                              + getEventTypeName(type) + "' for role '" + role.getName() + "'";
438             logMessage(this, LogService.LOG_ERROR,
439                        message);
440         }
441     }
442 
443     /**
444      * @see UserAdminUtil#checkPermission(String, String)
445      */
446     public void checkPermission(String name, String action) {
447         SecurityManager sm = System.getSecurityManager();
448         if (null != sm) {
449             sm.checkPermission(new UserAdminPermission(name, action));
450         }
451     }
452     
453     /**
454      * @see UserAdminUtil#encrypt(Object)
455      */
456     public byte[] encrypt(Object value) {
457         byte[] valueBytes = null;
458         if (value instanceof String) {
459             valueBytes = ((String)value).getBytes();
460         } else if (value instanceof byte[]) {
461             valueBytes = (byte[]) value;
462         } else {
463             throw new IllegalArgumentException("Illegal value type: " + value.getClass().getName());
464         }
465         //
466         if (null != m_encryptor) {
467             valueBytes = m_encryptor.encrypt(valueBytes);
468         }
469         return valueBytes;
470     }
471     
472     /**
473      * @see UserAdminUtil#verifyEncryptedValue(Object, byte[])
474      */
475     public boolean compareToEncryptedValue(Object inputValue,
476                                         byte[] storedValue) {
477         byte[] valueBytes = null;
478         if (inputValue instanceof String) {
479             valueBytes = ((String)inputValue).getBytes();
480         } else if (inputValue instanceof byte[]) {
481             valueBytes = (byte[]) inputValue;
482         } else {
483             throw new IllegalArgumentException("Illegal value type: " + inputValue.getClass().getName());
484         }
485         //
486         if (null != m_encryptor) {
487             return m_encryptor.compare(valueBytes, storedValue);
488         }
489         return Arrays.equals(valueBytes, storedValue);
490     }
491     
492     // UserAdminFactory interface
493     
494     /**
495      * @see UserAdminFactory#createUser(String, Map, Map)
496      */
497     public User createUser(String name,
498                            Map<String, Object> properties,
499                            Map<String, Object> credentials) {
500         UserImpl user = new UserImpl(name, this, properties, credentials);
501         return user;
502     }
503 
504     /**
505      * @see UserAdminFactory#createGroup(String, Map, Map)
506      */
507     public Group createGroup(String name,
508                              Map<String, Object> properties,
509                              Map<String, Object> credentials) {
510         GroupImpl group = new GroupImpl(name, this, properties, credentials);
511         return group;
512     }
513 }