View Javadoc

1   /****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.james.jcr;
21  
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Iterator;
25  
26  import javax.jcr.Credentials;
27  import javax.jcr.Node;
28  import javax.jcr.NodeIterator;
29  import javax.jcr.PathNotFoundException;
30  import javax.jcr.Repository;
31  import javax.jcr.RepositoryException;
32  import javax.jcr.Session;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.james.api.user.User;
37  import org.apache.james.api.user.UsersRepository;
38  
39  
40  public class JCRUsersRepository extends AbstractJCRRepository implements UsersRepository {
41      
42      //TODO: Add namespacing
43      private static final String PASSWD_PROPERTY = "passwd";
44  
45      private static final String USERNAME_PROPERTY = "username";
46  
47      private static final Log LOGGER = LogFactory.getLog(JCRMailRepository.class);
48      
49      /**
50       * For setter injection.
51       */    
52      public JCRUsersRepository() {
53          super(LOGGER);
54          this.path = "users";
55      }
56  
57      /**
58       * Maximal constructor for injection.
59       * @param repository not null
60       * @param credentials login credentials for accessing the repository
61       * or null to use default credentials
62       * @param workspace name of the workspace used as the mail repository.
63       * or null to use default workspace
64       * @param path path (relative to root) of the user node within the workspace,
65       * or null to use default.
66       */
67      public JCRUsersRepository(Repository repository, Credentials credentials, String workspace, String path, Log logger) {
68          super(repository, credentials, workspace, path, logger);
69      }
70      
71      /**
72       * Minimal constructor for injection.
73       * @param repository not null
74       */
75      public JCRUsersRepository(Repository repository) {
76          super(repository, LOGGER);
77          this.path = "users";
78      }
79  
80      /**
81       * Adds a user to the repository with the specified User object.
82       *
83       * @param user the user to be added
84       *
85       * @return true if succesful, false otherwise
86       * @since James 1.2.2
87       * 
88       * @deprecated James 2.4 user should be added using username/password
89       * because specific implementations of UsersRepository will support specific 
90       * implementations of users object.
91       */
92      public boolean addUser(User user) {
93          throw new UnsupportedOperationException("Unsupported by JCR");
94      }
95  
96      /**
97       * Adds a user to the repository with the specified attributes.  In current
98       * implementations, the Object attributes is generally a String password.
99       *
100      * @param name the name of the user to be added
101      * @param attributes see decription
102      * 
103      * @deprecated James 2.4 user is always added using username/password and
104      * eventually modified by retrieving it later.
105      */
106     public void addUser(String name, Object attributes) {
107         if (attributes instanceof String) {
108             addUser(name, (String) attributes);
109         } else {
110             throw new IllegalArgumentException("Expected password string");
111         }
112     }
113     
114     /**
115      * Adds a user to the repository with the specified password
116      * 
117      * @param username the username of the user to be added
118      * @param password the password of the user to add
119      * @return true if succesful, false otherwise
120      * 
121      * @since James 2.3.0
122      */
123     public boolean addUser(String username, String password) {
124 
125         try {
126             final Session session = login();
127             try {
128                 final String name = toSafeName(username);
129                 final String path = this.path + "/" + name;
130                 final Node rootNode = session.getRootNode();
131                 try {
132                     rootNode.getNode(path);
133                     logger.info("User already exists");
134                     return false;
135                 } catch (PathNotFoundException e) {
136                     // user does not exist
137                 }
138                 Node parent;
139                 try {
140                     parent = rootNode.getNode(this.path);
141                 } catch (PathNotFoundException e) {
142                     // TODO: Need to consider whether should insist that parent
143                     // TODO: path exists.
144                     parent = rootNode.addNode(this.path);
145                 }
146                 
147                 Node node = parent.addNode(name);
148                 node.setProperty(USERNAME_PROPERTY, username);
149                 final String hashedPassword;
150                 if (password == null)
151                 {
152                     // Support easy password reset
153                     hashedPassword = "";
154                 }
155                 else
156                 {
157                     hashedPassword = JCRUser.hashPassword(username, password);
158                 }
159                 node.setProperty(PASSWD_PROPERTY, hashedPassword);
160                 session.save();
161                 return true;
162             } finally {
163                 session.logout();
164             }
165             
166         } catch (RepositoryException e) {
167             if (logger.isInfoEnabled()) {
168                 logger.info("Failed to add user: " + username, e);
169             }
170         }
171 
172         return false;
173     }
174 
175     /**
176      * Get the user object with the specified user name.  Return null if no
177      * such user.
178      *
179      * @param name the name of the user to retrieve
180      * @return the user being retrieved, null if the user doesn't exist
181      *
182      * @since James 1.2.2
183      */
184     public User getUserByName(String username) {
185         User user;
186         try {
187             final Session session = login();
188             try {
189                 final String name = toSafeName(username);
190                 final String path = this.path + "/" + name;
191                 final Node rootNode = session.getRootNode();
192                 
193                 try {
194                     final Node node = rootNode.getNode(path);
195                     user = new JCRUser(node.getProperty(USERNAME_PROPERTY).getString(), 
196                             node.getProperty(PASSWD_PROPERTY).getString());
197                 } catch (PathNotFoundException e) {
198                     // user not found
199                     user = null;
200                 }
201             } finally {
202                 session.logout();
203             }
204             
205         } catch (RepositoryException e) {
206             if (logger.isInfoEnabled()) {
207                 logger.info("Failed to add user: " + username, e);
208             }
209             user = null;
210         }
211         return user;
212     }
213 
214     /**
215      * Get the user object with the specified user name. Match user naems on
216      * a case insensitive basis.  Return null if no such user.
217      *
218      * @param name the name of the user to retrieve
219      * @return the user being retrieved, null if the user doesn't exist
220      *
221      * @since James 1.2.2
222      * @deprecated James 2.4 now caseSensitive is a property of the repository
223      * implementations and the getUserByName will search according to this property.
224      */
225     public User getUserByNameCaseInsensitive(String name) {
226         throw new UnsupportedOperationException();
227     }
228 
229     /**
230      * Returns the user name of the user matching name on an equalsIgnoreCase
231      * basis. Returns null if no match.
232      *
233      * @param name the name to case-correct
234      * @return the case-correct name of the user, null if the user doesn't exist
235      */
236     public String getRealName(String name) {
237         return null;
238     }
239 
240     /**
241      * Update the repository with the specified user object. A user object
242      * with this username must already exist.
243      *
244      * @return true if successful.
245      */
246     public boolean updateUser(final User user) {
247         if (user != null && user instanceof JCRUser)
248         {
249             final JCRUser jcrUser = (JCRUser) user;
250             final String userName = jcrUser.getUserName();
251             try {
252                 final Session session = login();
253                 try {
254                     final String name = toSafeName(userName);
255                     final String path = this.path + "/" + name;
256                     final Node rootNode = session.getRootNode();
257                     
258                     try {
259                         final String hashedSaltedPassword = jcrUser.getHashedSaltedPassword();
260                         rootNode.getNode(path).setProperty(PASSWD_PROPERTY, hashedSaltedPassword);
261                         session.save();
262                         return true;
263                     } catch (PathNotFoundException e) {
264                         // user not found
265                         logger.debug("User not found");
266                     }
267                 } finally {
268                     session.logout();
269                 }
270                 
271             } catch (RepositoryException e) {
272                 if (logger.isInfoEnabled()) {
273                     logger.info("Failed to add user: " + userName, e);
274                 }
275             }
276         }
277         return false;
278     }
279 
280     /**
281      * Removes a user from the repository
282      *
283      * @param name the user to remove from the repository
284      */
285     public void removeUser(String username) {
286         try {
287             final Session session = login();
288             try {
289                 final String name = toSafeName(username);
290                 final String path = this.path + "/" + name;
291                 try {
292                     session.getRootNode().getNode(path).remove();
293                     session.save();
294                 } catch (PathNotFoundException e) {
295                     // user not found
296                 }
297             } finally {
298                 session.logout();
299             }
300             
301         } catch (RepositoryException e) {
302             if (logger.isInfoEnabled()) {
303                 logger.info("Failed to add user: " + username, e);
304             }
305         }
306     }
307 
308     /**
309      * Returns whether or not this user is in the repository
310      *
311      * @param name the name to check in the repository
312      * @return whether the user is in the repository
313      */
314     public boolean contains(String name) {
315         try {
316             final Session session = login();
317             try {
318                 final Node rootNode = session.getRootNode();
319                 final String path = this.path + "/" + toSafeName(name);                
320                 rootNode.getNode(path);
321                 return true;
322             } finally {
323                 session.logout();
324             }
325             
326         } catch (RepositoryException e) {
327             if (logger.isDebugEnabled()) {
328                 logger.debug("User not found: " + name, e);
329             }
330         }
331 
332         return false;
333     }
334 
335     /**
336      * Returns whether or not this user is in the repository. Names are
337      * matched on a case insensitive basis.
338      *
339      * @param name the name to check in the repository
340      * @return whether the user is in the repository
341      * 
342      * @deprecated James 2.4 now caseSensitive is a property of the repository
343      * implementations and the contains will search according to this property.
344      */
345     public boolean containsCaseInsensitive(String name) {
346         throw new UnsupportedOperationException();
347     }
348 
349     /**
350      * Test if user with name 'name' has password 'password'.
351      *
352      * @param name the name of the user to be tested
353      * @param password the password to be tested
354      *
355      * @return true if the test is successful, false if the user
356      *              doesn't exist or if the password is incorrect
357      *
358      * @since James 1.2.2
359      */
360     public boolean test(String username, String password) {
361         try {
362             final Session session = login();
363             try {
364                 final String name = toSafeName(username);
365                 final String path = this.path + "/" + name;
366                 final Node rootNode = session.getRootNode();
367                 
368                 try {
369                     final Node node = rootNode.getNode(path);
370                     final String current = node.getProperty(PASSWD_PROPERTY).getString();
371                     if (current == null || current == "")
372                     {
373                         return password == null || password == "";
374                     }
375                     final String hashPassword = JCRUser.hashPassword(username, password);
376                     return current.equals(hashPassword);
377                 } catch (PathNotFoundException e) {
378                     // user not found
379                     logger.debug("User not found");
380                     return false;
381                 }
382             } finally {
383                 session.logout();
384             }
385             
386         } catch (RepositoryException e) {
387             if (logger.isInfoEnabled()) {
388                 logger.info("Failed to add user: " + username, e);
389             }
390             return false;
391         }
392 
393     }
394 
395     /**
396      * Returns a count of the users in the repository.
397      *
398      * @return the number of users in the repository
399      */
400     public int countUsers() {
401         try {
402             final Session session = login();
403             try {
404                 final Node rootNode = session.getRootNode();
405                 try {
406                     final Node node = rootNode.getNode(path);
407                     //TODO: Use query
408                     //TODO: Use namespacing to avoid unwanted nodes
409                     NodeIterator it = node.getNodes();
410                     return (int) it.getSize();
411                 } catch (PathNotFoundException e) {
412                     return 0;
413                 }
414             } finally {
415                 session.logout();
416             }
417         } catch (RepositoryException e) {
418             if (logger.isInfoEnabled()) {
419                 logger.info("Failed to count user", e);
420             }
421             return 0;
422         }
423     }
424 
425     /**
426      * List users in repository.
427      *
428      * @return Iterator over a collection of Strings, each being one user in the repository.
429      */
430     public Iterator list() {
431         final Collection userNames = new ArrayList();
432         try {
433             final Session session = login();
434             try {
435                 final Node rootNode = session.getRootNode();
436                 try {
437                     final Node baseNode = rootNode.getNode(path);
438                     //TODO: Use query
439                     final NodeIterator it = baseNode.getNodes();
440                     while(it.hasNext()) {
441                         final Node node = it.nextNode();
442                         try {
443                             final String userName = node.getProperty(USERNAME_PROPERTY).getString();
444                             userNames.add(userName);
445                         } catch (PathNotFoundException e) {
446                             logger.info("Node missing user name. Ignoring.");
447                         }
448                     }
449                 } catch (PathNotFoundException e) {
450                     logger.info("Path not found. Forgotten to setup the repository?");
451                 }
452             } finally {
453                 session.logout();
454             }
455         } catch (RepositoryException e) {
456             if (logger.isInfoEnabled()) {
457                 logger.info("Failed to count user", e);
458             }
459         }
460         return userNames.iterator();
461     }
462 }