View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.security;
35  
36  import info.magnolia.cms.beans.config.ContentRepository;
37  import info.magnolia.cms.core.Content;
38  import info.magnolia.cms.core.HierarchyManager;
39  import info.magnolia.cms.core.ItemType;
40  import info.magnolia.cms.core.MetaData;
41  import info.magnolia.cms.core.Path;
42  import info.magnolia.cms.core.search.Query;
43  import info.magnolia.cms.core.search.QueryManager;
44  import info.magnolia.cms.security.auth.Entity;
45  import info.magnolia.context.MgnlContext;
46  
47  import java.util.ArrayList;
48  import java.util.Collection;
49  import java.util.Iterator;
50  import java.util.Set;
51  
52  import javax.jcr.PathNotFoundException;
53  import javax.jcr.RepositoryException;
54  import javax.security.auth.Subject;
55  
56  import org.apache.commons.codec.binary.Base64;
57  import org.apache.commons.lang.StringUtils;
58  import org.slf4j.Logger;
59  import org.slf4j.LoggerFactory;
60  
61  
62  /**
63   * Manages the users stored in Magnolia itself.
64   * @version $Revision: 48411 $ ($Author: ochytil $)
65   */
66  public class MgnlUserManager implements UserManager {
67  
68      private static final Logger log = LoggerFactory.getLogger(MgnlUserManager.class);
69  
70      public static final String PROPERTY_EMAIL = "email";
71      public static final String PROPERTY_LANGUAGE = "language";
72      public static final String PROPERTY_LASTACCESS = "lastaccess";
73      public static final String PROPERTY_PASSWORD = "pswd";
74      public static final String PROPERTY_TITLE = "title";
75  
76      public static final String NODE_ACLUSERS = "acl_users";
77  
78      private String realmName;
79  
80      private int maxFailedLoginAttempts;
81      
82      private long lockTimePeriod;
83  
84      /**
85       * Do not instantiate it!
86       */
87      public MgnlUserManager() {
88      }
89  
90      // TODO : rename to getRealmName and setRealmName (and make sure Content2Bean still sets realmName using the
91      // parent's node name)
92      public String getName() {
93          return getRealmName();
94      }
95  
96      public void setName(String name) {
97          this.realmName = name;
98      }
99  
100     public String getRealmName() {
101         return realmName;
102     }
103 
104     public void setMaxFailedLoginAttempts(int maxFailedLoginAttempts){
105         this.maxFailedLoginAttempts = maxFailedLoginAttempts;
106     }
107 
108     public int getMaxFailedLoginAttempts(){
109         return maxFailedLoginAttempts;
110     }
111     
112     public void setLockTimePeriod(long lockTimePeriod){
113         this.lockTimePeriod = lockTimePeriod;
114     }
115     
116     public long getLockTimePeriod(){
117         return lockTimePeriod;
118     }
119 
120     /**
121      * Get the user object. Uses a search
122      * @param name
123      * @return the user object
124      */
125     public User getUser(String name) {
126         try {
127             return getFromRepository(name);
128         }
129         catch (RepositoryException e) {
130             log.info("Unable to load user [\"{} \"] due to: ", name, e);
131             return null;
132         }
133     }
134 
135     public User getUser(Subject subject) throws UnsupportedOperationException {
136         // this could be the case if no one is logged in yet
137         if (subject == null) {
138             log.debug("subject not set.");
139             return new DummyUser();
140         }
141 
142         Set<Entity> principalSet = subject.getPrincipals(Entity.class);
143         Iterator<Entity> entityIterator = principalSet.iterator();
144         if (!entityIterator.hasNext()) {
145             // happens when JCR authentication module set to optional and user doesn't exist in magnolia
146             log.debug("user name not contained in principal set.");
147             return new DummyUser();
148         }
149         Entity userDetails = entityIterator.next();
150         String name = (String) userDetails.getProperty(Entity.NAME);
151         try {
152             return getFromRepository(name);
153         }
154         catch (PathNotFoundException e) {
155             log.error("user not registered in magnolia itself [\"{}\"]", name);
156         }
157         catch (Exception e) {
158             log.error("can't get jcr-node of current user", e);
159         }
160 
161         return new DummyUser();
162     }
163 
164     protected User getFromRepository(String name) throws RepositoryException {
165         final Content node = findUserNode(this.realmName, name);
166         if (node == null) {
167             log.debug("User not found: [{}]", name);
168             return null;
169         }
170 
171         return newUserInstance(node);
172     }
173 
174     /**
175      * Helper method to find a user in a certain realm. Uses JCR Query.
176      */
177     protected Content findUserNode(String realm, String name) throws RepositoryException {
178         String where = "where jcr:path = '/" + realm + "/" + name + "'";
179         where += " or jcr:path like '/" + realm + "/%/" + name + "'";
180 
181         // the all realm searches the repository
182         if (Realm.REALM_ALL.equals(realm)) {
183             where = "where jcr:path like '%/" + name + "'";
184         }
185 
186         String statement = "select * from " + ItemType.USER + " " + where;
187 
188         QueryManager qm = getHierarchyManager().getQueryManager();
189         Query query = qm.createQuery(statement, Query.SQL);
190         Collection<Content> users = query.execute().getContent(ItemType.USER.getSystemName());
191         if (users.size() == 1) {
192             return users.iterator().next();
193         }
194         else if (users.size() > 1) {
195             log.error("More than one user found with name [{}] in realm [{}]", name, realm);
196         }
197         return null;
198     }
199 
200     /**
201      * SystemUserManager does this.
202      */
203     public User getSystemUser() throws UnsupportedOperationException {
204         throw new UnsupportedOperationException();
205     }
206 
207     /**
208      * SystemUserManager does this.
209      */
210     public User getAnonymousUser() throws UnsupportedOperationException {
211         throw new UnsupportedOperationException();
212     }
213 
214     /**
215      * Get all users managed by this user manager.
216      */
217     public Collection<User> getAllUsers() {
218         Collection<User> users = new ArrayList<User>();
219         try {
220             Content root = getHierarchyManager().getRoot().getContent(getRealmName());
221             updateUserListWithAllChildren(root, users);
222         }
223         catch (Exception e) {
224             log.error("can't find user");
225         }
226         return users;
227     }
228     
229     /**
230      * Updates collection with all users located under provided node.
231      */
232     public void updateUserListWithAllChildren(Content node, Collection<User> users){
233         Collection<Content> nodes = node.getChildren(ItemType.USER);
234         Collection<Content> folders = node.getChildren(ItemType.NT_FOLDER);
235         if(!nodes.isEmpty()){
236             for (Content userNode : nodes) {
237                 users.add(newUserInstance(userNode));
238             }
239         }
240         if(!folders.isEmpty()){
241             Iterator<Content> it = folders.iterator();
242             while(it.hasNext()){
243                 updateUserListWithAllChildren((Content)it.next(), users);
244             }
245         }
246     }
247 
248     public User createUser(String name, String pw) {
249         validateUsername(name);
250         try {
251             final Content node = createUserNode(name);
252             node.createNodeData("name").setValue(name);
253             setPasswordProperty(node, pw);
254             node.createNodeData("language").setValue("en");
255 
256             final String handle = node.getHandle();
257             final Content acls = node.createContent(NODE_ACLUSERS, ItemType.CONTENTNODE);
258             // read only access to the node itself
259             Content acl = acls.createContent(Path.getUniqueLabel(acls.getHierarchyManager(), acls.getHandle(), "0"), ItemType.CONTENTNODE);
260             acl.setNodeData("path", handle);
261             acl.setNodeData("permissions", new Long(Permission.READ));
262             // those who had access to their nodes should get access to their own props
263             addWrite(handle, PROPERTY_EMAIL, acls);
264             addWrite(handle, PROPERTY_LANGUAGE, acls);
265             addWrite(handle, PROPERTY_LASTACCESS, acls);
266             addWrite(handle, PROPERTY_PASSWORD, acls);
267             addWrite(handle, PROPERTY_TITLE, acls);
268             // and of course the meta data
269             addWrite(handle, MetaData.DEFAULT_META_NODE, acls);
270 
271             getHierarchyManager().save();
272             return newUserInstance(node);
273         }
274         catch (Exception e) {
275             log.info("can't create user [" + name + "]", e);
276             return null;
277         }
278     }
279 
280     public void changePassword(User user, String newPassword) {
281         final Content userNode = ((MgnlUser) user).getUserNode();
282         try {
283             setPasswordProperty(userNode, newPassword);
284             userNode.save();
285         }
286         catch (RepositoryException e) {
287             throw new RuntimeException(e); // TODO
288         }
289     }
290 
291     protected void setPasswordProperty(Content userNode, String clearPassword) throws RepositoryException {
292         userNode.createNodeData(PROPERTY_PASSWORD).setValue(encodePassword(clearPassword));
293     }
294 
295     protected String encodePassword(String clearPassword) {
296         return new String(Base64.encodeBase64(clearPassword.getBytes()));
297     }
298 
299     protected void validateUsername(String name) {
300         if (StringUtils.isBlank(name)) {
301             throw new IllegalArgumentException(name + " is not a valid username.");
302         }
303     }
304 
305     protected Content createUserNode(String name) throws RepositoryException {
306         final String path = "/" + getRealmName();
307         return getHierarchyManager().createContent(path, name, ItemType.USER.getSystemName());
308     }
309 
310     /**
311      * Return the HierarchyManager for the user workspace (through the system context).
312      */
313     protected HierarchyManager getHierarchyManager() {
314         return MgnlContext.getSystemContext().getHierarchyManager(ContentRepository.USERS);
315     }
316 
317     /**
318      * @deprecated since 4.3.1 - use {@link #newUserInstance(info.magnolia.cms.core.Content)}
319      */
320     protected MgnlUser userInstance(Content node) {
321         return new MgnlUser(node);
322     }
323 
324     /**
325      * Creates a {@link MgnlUser} out of a jcr node. Can be overridden in order to provide a different implementation.
326      * @since 4.3.1
327      */
328     protected User newUserInstance(Content node) {
329         return userInstance(node);
330     }
331 
332     private Content addWrite(String parentPath, String property, Content acls) throws PathNotFoundException, RepositoryException, AccessDeniedException {
333         Content acl = acls.createContent(Path.getUniqueLabel(acls.getHierarchyManager(), acls.getHandle(), "0"), ItemType.CONTENTNODE);
334         acl.setNodeData("path", parentPath + "/" + property);
335         acl.setNodeData("permissions", new Long(Permission.ALL));
336         return acl;
337     }
338 }