View Javadoc

1   /**
2    * This file Copyright (c) 2003-2010 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: 36890 $ ($Author: pbaerfuss $)
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      /**
81       * Do not instantiate it!
82       */
83      public MgnlUserManager() {
84      }
85  
86      // TODO : rename to getRealmName and setRealmName (and make sure Content2Bean still sets realmName using the
87      // parent's node name)
88      public String getName() {
89          return getRealmName();
90      }
91  
92      public void setName(String name) {
93          this.realmName = name;
94      }
95  
96      public String getRealmName() {
97          return realmName;
98      }
99  
100     /**
101      * Get the user object. Uses a search
102      * @param name
103      * @return the user object
104      */
105     public User getUser(String name) {
106         try {
107             return getFromRepository(name);
108         }
109         catch (RepositoryException e) {
110             log.info("Unable to load user [" + name + "] due to: " + e.toString(), e);
111             return null;
112         }
113     }
114 
115     public User getUser(Subject subject) throws UnsupportedOperationException {
116         // this could be the case if no one is logged in yet
117         if (subject == null) {
118             log.debug("subject not set.");
119             return new DummyUser();
120         }
121 
122         Set<Entity> principalSet = subject.getPrincipals(Entity.class);
123         Iterator<Entity> entityIterator = principalSet.iterator();
124         if (!entityIterator.hasNext()) {
125             // happens when JCR authentication module set to optional and user doesn't exist in magnolia
126             log.debug("user name not contained in principal set.");
127             return new DummyUser();
128         }
129         Entity userDetails = entityIterator.next();
130         String name = (String) userDetails.getProperty(Entity.NAME);
131         try {
132             return getFromRepository(name);
133         }
134         catch (PathNotFoundException e) {
135             log.error("user not registered in magnolia itself [" + name + "]");
136         }
137         catch (Exception e) {
138             log.error("can't get jcr-node of current user", e);
139         }
140 
141         return new DummyUser();
142     }
143 
144     protected User getFromRepository(String name) throws RepositoryException {
145         final Content node = findUserNode(this.realmName, name);
146         if (node == null) {
147             log.debug("User not found: [{}]", name);
148             return null;
149         }
150 
151         return newUserInstance(node);
152     }
153 
154     /**
155      * Helper method to find a user in a certain realm. Uses JCR Query.
156      */
157     protected Content findUserNode(String realm, String name) throws RepositoryException {
158         String where = "where jcr:path = '/" + realm + "/" + name + "'";
159         where += " or jcr:path like '/" + realm + "/%/" + name + "'";
160 
161         // the all realm searches the repository
162         if (Realm.REALM_ALL.equals(realm)) {
163             where = "where jcr:path like '%/" + name + "'";
164         }
165 
166         String statement = "select * from " + ItemType.USER + " " + where;
167 
168         QueryManager qm = getHierarchyManager().getQueryManager();
169         Query query = qm.createQuery(statement, Query.SQL);
170         Collection<Content> users = query.execute().getContent(ItemType.USER.getSystemName());
171         if (users.size() == 1) {
172             return users.iterator().next();
173         }
174         else if (users.size() > 1) {
175             log.error("More than one user found with name [{}] in realm [{}]");
176         }
177         return null;
178     }
179 
180     /**
181      * SystemUserManager does this.
182      */
183     public User getSystemUser() throws UnsupportedOperationException {
184         throw new UnsupportedOperationException();
185     }
186 
187     /**
188      * SystemUserManager does this.
189      */
190     public User getAnonymousUser() throws UnsupportedOperationException {
191         throw new UnsupportedOperationException();
192     }
193 
194     /**
195      * Get all users managed by this user manager.
196      */
197     public Collection<User> getAllUsers() {
198         Collection<User> users = new ArrayList<User>();
199         try {
200             Collection<Content> nodes = getHierarchyManager().getRoot().getChildren(ItemType.USER);
201             for (Content node : nodes) {
202                 users.add(newUserInstance(node));
203             }
204         }
205         catch (Exception e) {
206             log.error("can't find user");
207         }
208         return users;
209     }
210 
211     public User createUser(String name, String pw) {
212         validateUsername(name);
213         try {
214             final Content node = createUserNode(name);
215             node.createNodeData("name").setValue(name);
216             setPasswordProperty(node, pw);
217             node.createNodeData("language").setValue("en");
218 
219             final String handle = node.getHandle();
220             final Content acls = node.createContent(NODE_ACLUSERS, ItemType.CONTENTNODE);
221             // read only access to the node itself
222             Content acl = acls.createContent(Path.getUniqueLabel(acls.getHierarchyManager(), acls.getHandle(), "0"), ItemType.CONTENTNODE);
223             acl.setNodeData("path", handle);
224             acl.setNodeData("permissions", new Long(Permission.READ));
225             // those who had access to their nodes should get access to their own props
226             addWrite(handle, PROPERTY_EMAIL, acls);
227             addWrite(handle, PROPERTY_LANGUAGE, acls);
228             addWrite(handle, PROPERTY_LASTACCESS, acls);
229             addWrite(handle, PROPERTY_PASSWORD, acls);
230             addWrite(handle, PROPERTY_TITLE, acls);
231             // and of course the meta data
232             addWrite(handle, MetaData.DEFAULT_META_NODE, acls);
233 
234             getHierarchyManager().save();
235             return newUserInstance(node);
236         }
237         catch (Exception e) {
238             log.info("can't create user [" + name + "]", e);
239             return null;
240         }
241     }
242 
243     public void changePassword(User user, String newPassword) {
244         final Content userNode = ((MgnlUser) user).getUserNode();
245         try {
246             setPasswordProperty(userNode, newPassword);
247             userNode.save();
248         }
249         catch (RepositoryException e) {
250             throw new RuntimeException(e); // TODO
251         }
252     }
253 
254     protected void setPasswordProperty(Content userNode, String clearPassword) throws RepositoryException {
255         userNode.createNodeData(PROPERTY_PASSWORD).setValue(encodePassword(clearPassword));
256     }
257 
258     protected String encodePassword(String clearPassword) {
259         return new String(Base64.encodeBase64(clearPassword.getBytes()));
260     }
261 
262     protected void validateUsername(String name) {
263         if (StringUtils.isBlank(name)) {
264             throw new IllegalArgumentException(name + " is not a valid username.");
265         }
266     }
267 
268     protected Content createUserNode(String name) throws RepositoryException {
269         final String path = "/" + getRealmName();
270         return getHierarchyManager().createContent(path, name, ItemType.USER.getSystemName());
271     }
272 
273     /**
274      * Return the HierarchyManager for the user workspace (through the system context).
275      */
276     protected HierarchyManager getHierarchyManager() {
277         return MgnlContext.getSystemContext().getHierarchyManager(ContentRepository.USERS);
278     }
279 
280     /**
281      * @deprecated since 4.3.1 - use {@link #newUserInstance(info.magnolia.cms.core.Content)}
282      */
283     protected MgnlUser userInstance(Content node) {
284         return new MgnlUser(node);
285     }
286 
287     /**
288      * Creates a {@link MgnlUser} out of a jcr node. Can be overridden in order to provide a different implementation.
289      * @since 4.3.1
290      */
291     protected User newUserInstance(Content node) {
292         return userInstance(node);
293     }
294 
295     private Content addWrite(String parentPath, String property, Content acls) throws PathNotFoundException, RepositoryException, AccessDeniedException {
296         Content acl = acls.createContent(Path.getUniqueLabel(acls.getHierarchyManager(), acls.getHandle(), "0"), ItemType.CONTENTNODE);
297         acl.setNodeData("path", parentPath + "/" + property);
298         acl.setNodeData("permissions", new Long(Permission.ALL));
299         return acl;
300     }
301 }