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