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.NodeData;
41  import info.magnolia.cms.core.Path;
42  import info.magnolia.cms.util.NodeDataUtil;
43  import info.magnolia.cms.util.SystemContentWrapper;
44  
45  import org.apache.commons.codec.binary.Base64;
46  import org.apache.commons.lang.StringUtils;
47  import org.apache.commons.lang.exception.ExceptionUtils;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  import javax.jcr.ItemNotFoundException;
52  import javax.jcr.PathNotFoundException;
53  import javax.jcr.PropertyType;
54  import javax.jcr.RepositoryException;
55  import java.io.Serializable;
56  import java.util.Collection;
57  import java.util.GregorianCalendar;
58  import java.util.Set;
59  import java.util.TreeSet;
60  
61  
62  /**
63   * This class wraps a user content object.
64   * @version $Revision: 36890 $ ($Author: pbaerfuss $)
65   */
66  public class MgnlUser extends AbstractUser implements Serializable {
67  
68      private static final long serialVersionUID = 222L;
69  
70      private static final Logger log = LoggerFactory.getLogger(MgnlUser.class);
71  
72      /**
73       * Under this subnodes the assigned roles are saved.
74       */
75      private static final String NODE_ROLES = "roles"; //$NON-NLS-1$
76  
77      private static final String NODE_GROUPS = "groups"; //$NON-NLS-1$
78  
79      /**
80       * Used to to synchronize write operations in {@link MgnlUser#setLastAccess().
81       */
82      private static final Object mutex = new Object();
83  
84      // serialized
85      private final SystemContentWrapper userNode;
86  
87      /**
88       * @param userNode the Content object representing this user
89       */
90      protected MgnlUser(Content userNode) {
91          this.userNode = new SystemContentWrapper(userNode);
92      }
93  
94      /**
95       * Is this user in a specified role?
96       * @param groupName the name of the role
97       * @return true if in role
98       */
99      public boolean inGroup(String groupName) {
100         return this.hasAny(groupName, NODE_GROUPS);
101     }
102 
103     /**
104      * Remove a group. Implementation is optional
105      * @param groupName
106      */
107     public void removeGroup(String groupName) throws UnsupportedOperationException {
108         this.remove(groupName, NODE_GROUPS);
109     }
110 
111     /**
112      * Adds this user to a group. Implementation is optional
113      * @param groupName
114      */
115     public void addGroup(String groupName) throws UnsupportedOperationException {
116         this.add(groupName, NODE_GROUPS);
117     }
118 
119     public boolean isEnabled() {
120         return NodeDataUtil.getBoolean(getUserNode(), "enabled", true);
121     }
122 
123     public void setEnabled(boolean enabled) {
124         try {
125             NodeDataUtil.getOrCreateAndSet(getUserNode(), "enabled", enabled);
126             getUserNode().save();
127         } catch (RepositoryException e) {
128             throw new RuntimeException(e); // TODO
129         }
130     }
131 
132     /**
133      * Is this user in a specified role?
134      * @param roleName the name of the role
135      * @return true if in role
136      */
137     public boolean hasRole(String roleName) {
138         return this.hasAny(roleName, NODE_ROLES);
139     }
140 
141     public void removeRole(String roleName) {
142         this.remove(roleName, NODE_ROLES);
143     }
144 
145     public void addRole(String roleName) {
146         this.add(roleName, NODE_ROLES);
147     }
148 
149     // TODO this are the same methods as in {@link MgnlGroup}. A rewrite is needed.
150 
151     private boolean hasAny(String name, String nodeName) {
152         try {
153             HierarchyManager hm;
154             if (StringUtils.equalsIgnoreCase(nodeName, NODE_ROLES)) {
155                 hm = MgnlSecurityUtil.getSystemHierarchyManager(ContentRepository.USER_ROLES);
156             }
157             else {
158                 hm = MgnlSecurityUtil.getSystemHierarchyManager(ContentRepository.USER_GROUPS);
159             }
160 
161             Content node = this.getUserNode().getContent(nodeName);
162             for (NodeData nodeData : node.getNodeDataCollection()) {
163                 // check for the existence of this ID
164                 try {
165                     if (hm.getContentByUUID(nodeData.getString()).getName().equalsIgnoreCase(name)) {
166                         return true;
167                     }
168                 }
169                 catch (ItemNotFoundException e) {
170                     log.debug("Role [{}] does not exist in the ROLES repository", name);
171                 }
172                 catch (IllegalArgumentException e) {
173                     log.debug("{} has invalid value", nodeData.getHandle());
174                 }
175             }
176         }
177         catch (RepositoryException e) {
178             log.debug(e.getMessage(), e);
179         }
180         return false;
181     }
182 
183     private void remove(String name, String nodeName) {
184         try {
185             HierarchyManager hm;
186             if (StringUtils.equalsIgnoreCase(nodeName, NODE_ROLES)) {
187                 hm = MgnlSecurityUtil.getContextHierarchyManager(ContentRepository.USER_ROLES);
188             }
189             else {
190                 hm = MgnlSecurityUtil.getContextHierarchyManager(ContentRepository.USER_GROUPS);
191             }
192             Content node = this.getUserNode().getContent(nodeName);
193 
194             for (NodeData nodeData : node.getNodeDataCollection()) {
195                 // check for the existence of this ID
196                 try {
197                     if (hm.getContentByUUID(nodeData.getString()).getName().equalsIgnoreCase(name)) {
198                         nodeData.delete();
199                     }
200                 }
201                 catch (ItemNotFoundException e) {
202                     log.debug("Role [{}] does not exist in the ROLES repository", name);
203                 }
204                 catch (IllegalArgumentException e) {
205                     log.debug("{} has invalid value", nodeData.getHandle());
206                 }
207             }
208             this.getUserNode().save();
209         }
210         catch (RepositoryException e) {
211             log.error("failed to remove " + name + " from user [" + this.getName() + "]", e);
212         }
213     }
214 
215     private void add(String name, String nodeName) {
216         try {
217             final String hmName;
218             if (StringUtils.equalsIgnoreCase(nodeName, NODE_ROLES)) {
219                 hmName = ContentRepository.USER_ROLES;
220             }
221             else {
222                 hmName = ContentRepository.USER_GROUPS;
223             }
224             final HierarchyManager hm = MgnlSecurityUtil.getContextHierarchyManager(hmName);
225 
226             if (!this.hasAny(name, nodeName)) {
227                if (!this.getUserNode().hasContent(nodeName)) {
228                     this.getUserNode().createContent(nodeName, ItemType.CONTENTNODE);
229                }
230                 Content node = this.getUserNode().getContent(nodeName);
231                 // add corresponding ID
232                 try {
233                     String value = hm.getContent("/" + name).getUUID(); // assuming that there is a flat hierarchy
234                     // used only to get the unique label
235                     HierarchyManager usersHM = MgnlSecurityUtil.getSystemHierarchyManager(ContentRepository.USERS);
236                     String newName = Path.getUniqueLabel(usersHM, node.getHandle(), "0");
237                     node.createNodeData(newName).setValue(value);
238                     this.getUserNode().save();
239                 }
240                 catch (PathNotFoundException e) {
241                     log.debug("[{}] does not exist in the {} repository", name, hmName);
242                 }
243             }
244         }
245         catch (RepositoryException e) {
246             log.error("failed to add " + name + " to user [" + this.getName() + "]", e);
247         }
248     }
249 
250     public String getName() {
251         return this.getUserNode().getName();
252     }
253 
254     public String getPassword() {
255         final String encodedPassword = this.getUserNode().getNodeData("pswd").getString().trim();
256         return decodePassword(encodedPassword);
257     }
258 
259     protected String decodePassword(String encodedPassword) {
260         return new String(Base64.decodeBase64(encodedPassword.getBytes()));
261     }
262 
263     public String getLanguage() {
264         return this.getUserNode().getNodeData("language").getString(); //$NON-NLS-1$
265     }
266 
267     public String getProperty(String propertyName) {
268         return NodeDataUtil.getString(getUserNode(), propertyName, null);
269     }
270 
271     public void setProperty(String propertyName, String value) {
272         try {
273             NodeDataUtil.getOrCreateAndSet(getUserNode(), propertyName, value);
274             getUserNode().save();
275         } catch (RepositoryException e) {
276             throw new RuntimeException(e); // TODO
277         }
278     }
279 
280     public Collection<String> getGroups() {
281         return MgnlSecurityUtil.collectPropertyNames(getUserNode(), "groups", ContentRepository.USER_GROUPS, false);
282     }
283 
284     public Collection<String> getAllGroups() {
285         final Set<String> allGroups = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
286         try {
287             // add the user's direct groups
288             final Collection<String> groups = getGroups();
289             allGroups.addAll(groups);
290 
291             // add all groups from direct groups
292             final GroupManager gm = SecuritySupport.Factory.getInstance().getGroupManager();
293             for (String groupName : groups) {
294                 final Group g = gm.getGroup(groupName);
295                 allGroups.addAll(g.getAllGroups());
296             }
297 
298             return allGroups;
299         } catch (AccessDeniedException e) {
300             throw new RuntimeException(e); // TODO
301         }
302     }
303 
304     public Collection<String> getRoles() {
305         return MgnlSecurityUtil.collectPropertyNames(getUserNode(), "roles", ContentRepository.USER_ROLES, false);
306     }
307 
308     public Collection<String> getAllRoles() {
309         final Set<String> allRoles = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
310         try {
311             // add the user's direct roles
312             allRoles.addAll(getRoles());
313 
314             // add roles from all groups
315             final GroupManager gm = SecuritySupport.Factory.getInstance().getGroupManager();
316             final Collection<String> allGroups = getAllGroups();
317             for (String groupName : allGroups) {
318                 final Group g = gm.getGroup(groupName);
319                 allRoles.addAll(g.getRoles());
320             }
321 
322             return allRoles;
323         } catch (AccessDeniedException e) {
324             throw new RuntimeException(e); // TODO
325         }
326     }
327 
328     /**
329      * Update the "last access" timestamp.
330      */
331     public void setLastAccess() {
332         NodeData lastaccess;
333         Exception finalException = null;
334         boolean success = false;
335         // try three times to save the lastaccess property
336         for(int i= 1; !success && i <=3; i++){
337             finalException = null;
338             try {
339                 // synchronize on a static mutex
340                 synchronized (mutex) {
341                     // refresh the session on retries
342                     if(i>1){
343                         getUserNode().refresh(false);
344                     }
345                     lastaccess = NodeDataUtil.getOrCreate(this.getUserNode(), "lastaccess", PropertyType.DATE);
346                     lastaccess.setValue(new GregorianCalendar());
347                     getUserNode().save();
348                     success = true;
349                 }
350             }
351             catch (RepositoryException e) {
352                 finalException = e;
353                 log.debug("Unable to set the last access", e);
354             }
355         }
356         if(finalException != null){
357             log.warn("Unable to set the last access date due to a " + ExceptionUtils.getMessage(finalException));
358         }
359     }
360 
361     public Content getUserNode() {
362         return userNode;
363     }
364 }