View Javadoc
1   /**
2    * This file Copyright (c) 2003-2015 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 static info.magnolia.cms.security.SecurityConstants.*;
37  
38  import info.magnolia.cms.core.Content;
39  
40  import java.io.Serializable;
41  import java.util.Calendar;
42  import java.util.Collection;
43  import java.util.Collections;
44  import java.util.Map;
45  import java.util.Set;
46  import java.util.TreeSet;
47  
48  import org.apache.jackrabbit.util.ISO8601;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  /**
53   * User for 4.5 instance
54   * In difference from old MgnlUser, this class operates directly on JCR session and with JCR nodes/properties as our hierarchy managers are not
55   * available at the login time.
56   * Also in difference from MgnlUser, this class doesn't keep around instance of the user node! TODO: Test performance impact of such change.
57   */
58  public class MgnlUser extends AbstractUser implements User, Serializable {
59  
60      private static final Logger log = LoggerFactory.getLogger(MgnlUser.class);
61  
62      private final Map<String, String> properties;
63      private final Collection<String> groups;
64      private final Collection<String> roles;
65  
66      private final String name;
67      private final String language;
68      private final String encodedPassword;
69      private boolean enabled = true;
70      private String path;
71      private String uuid;
72  
73      private final String realm;
74  
75  
76      public MgnlUser(String name, String realm, Collection<String> groups, Collection<String> roles, Map<String, String> properties) {
77          this.name = name;
78          this.roles = Collections.unmodifiableCollection(roles);
79          this.groups = Collections.unmodifiableCollection(groups);
80          this.properties = Collections.unmodifiableMap(properties);
81          this.realm = realm;
82  
83          //shortcut some often accessed props so we don't have to search hashmap for them.
84          language = properties.get(MgnlUserManager.PROPERTY_LANGUAGE);
85          String enbld = properties.get(MgnlUserManager.PROPERTY_ENABLED);
86          // all accounts are enabled by default and prop doesn't exist if the account was not disabled before
87          enabled = enbld == null ? true : Boolean.parseBoolean(properties.get(MgnlUserManager.PROPERTY_ENABLED));
88          encodedPassword = properties.get(MgnlUserManager.PROPERTY_PASSWORD);
89      }
90  
91      public MgnlUser(String name, String realm, Collection<String> groups, Collection<String> roles, Map<String, String> properties, String path, String uuid) {
92          this(name, realm, groups, roles, properties);
93          this.path = path;
94          this.uuid = uuid;
95      }
96  
97      /**
98       * Is this user in a specified group?
99       *
100      * @param groupName the name of the group
101      * @return true if in group
102      */
103     @Override
104     public boolean inGroup(String groupName) {
105         log.debug("inGroup({})", groupName);
106         return SecuritySupport.Factory.getInstance().getUserManager(getRealm()).hasAny(getName(), groupName, NODE_GROUPS);
107     }
108 
109     /**
110      * Remove a group. Implementation is optional
111      */
112     @Override
113     public void removeGroup(String groupName) throws UnsupportedOperationException {
114         log.debug("removeGroup({})", groupName);
115         throw new UnsupportedOperationException("use manager to remove groups!");
116     }
117 
118     /**
119      * Adds this user to a group. Implementation is optional
120      *
121      * @param groupName group the user should be added to
122      */
123     @Override
124     public void addGroup(String groupName) throws UnsupportedOperationException {
125         log.debug("addGroup({})", groupName);
126         throw new UnsupportedOperationException("use manager to add groups!");
127     }
128 
129     @Override
130     public boolean isEnabled() {
131         log.debug("isEnabled()");
132         return enabled;
133     }
134 
135     /**
136      * This methods sets flag just on the bean. It does not update persisted user data. Use manager to update user data.
137      *
138      * @deprecated since 4.5, use {@link UserManager#setProperty(User, String, String)} instead
139      */
140     @Override
141     @Deprecated
142     public void setEnabled(boolean enabled) {
143         log.debug("setEnabled({})", enabled);
144         throw new UnsupportedOperationException("use manager to enable user!");
145     }
146 
147     /**
148      * Is this user in a specified role?
149      *
150      * @param roleName the name of the role
151      * @return true if in role
152      */
153     @Override
154     public boolean hasRole(String roleName) {
155         return SecuritySupport.Factory.getInstance().getUserManager(getRealm()).hasAny(getName(), roleName, NODE_ROLES);
156     }
157 
158     @Override
159     public void removeRole(String roleName) throws UnsupportedOperationException {
160         log.debug("removeRole({})", roleName);
161         throw new UnsupportedOperationException("use manager to remove roles!");
162     }
163 
164     @Override
165     public void addRole(String roleName) throws UnsupportedOperationException {
166         log.debug("addRole({})", roleName);
167         throw new UnsupportedOperationException("use manager to add roles!");
168     }
169 
170     public int getFailedLoginAttempts() {
171         try {
172             return Integer.valueOf(this.properties.get("failedLoginAttempts"));
173         } catch (Exception e) {
174             return 0;
175         }
176     }
177 
178     public Calendar getReleaseTime() {
179         try {
180             return ISO8601.parse(this.properties.get("releaseTime"));
181         } catch (Exception e) {
182             return null;
183         }
184     }
185 
186     @Override
187     public String getName() {
188         log.debug("getName()=>{}", name);
189         return name;
190     }
191 
192     @Override
193     public String getPassword() {
194         return encodedPassword;
195     }
196 
197     @Deprecated
198     /**
199      * @deprecated Since 4.5.8. Password is now encoded by BCrypt and therefore cannot be decoded.
200      */
201     protected String decodePassword(String encodedPassword) {
202         throw new UnsupportedOperationException();
203     }
204 
205     @Override
206     public String getLanguage() {
207         log.debug("getLang()=>{}", language);
208         return this.language;
209     }
210 
211     @Override
212     public String getProperty(String propertyName) {
213         log.debug("getProperty({})", propertyName);
214         return properties.get(propertyName);
215     }
216 
217     @Override
218     public Collection<String> getGroups() {
219         log.debug("getGroups()");
220         return groups;
221     }
222 
223     @Override
224     public Collection<String> getAllGroups() {
225         // TODO: if the user is just a simple bean, then this method doesn't belong here anymore!!!!
226         // should be moved to user manager or to group manager???
227         log.debug("get groups for {}", getName());
228 
229         final Set<String> allGroups = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
230         final Collection<String> groups = getGroups();
231 
232         // FYI: can't initialize upfront as the instance of the user class needs to be created BEFORE repo is ready
233         GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager();
234 
235         // add all subbroups
236         addSubgroups(allGroups, man, groups);
237 
238         return allGroups;
239     }
240 
241     @Override
242     public Collection<String> getRoles() {
243         log.debug("getRoles()");
244         return roles;
245     }
246 
247     @Override
248     public Collection<String> getAllRoles() {
249         // TODO: if the user is just a simple bean, then this method doesn't belong here anymore!!!!
250         log.debug("get roles for {}", getName());
251 
252         final Set<String> allRoles = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
253         final Collection<String> roles = getRoles();
254 
255         // add all direct user groups
256         allRoles.addAll(roles);
257 
258         Collection<String> allGroups = getAllGroups();
259 
260         // FYI: can't initialize upfront as the instance of the user class needs to be created BEFORE repo is ready
261         GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager();
262 
263         // add roles from all groups
264         for (String group : allGroups) {
265             try {
266                 allRoles.addAll(man.getGroup(group).getRoles());
267             } catch (AccessDeniedException e) {
268                 log.debug("Skipping denied group {} for user {}", group, getName(), e);
269             } catch (UnsupportedOperationException e) {
270                 log.debug("Skipping unsupported  getGroup() for group {} and user {}", group, getName(), e);
271             }
272         }
273         return allRoles;
274     }
275 
276     public String getPath() {
277         return this.path;
278     }
279 
280     @Deprecated
281     public void setPath(String path) {
282         this.path = path;
283     }
284 
285     /**
286      * Any group from the groups is checked for the subgroups only if it is not in the allGroups yet. This is to prevent infinite loops in case of cyclic group assignment.
287      */
288     private void addSubgroups(final Set<String> allGroups, GroupManager man, Collection<String> groups) {
289         for (String groupName : groups) {
290             // check if this group was not already added to prevent infinite loops
291             if (!allGroups.contains(groupName)) {
292                 allGroups.add(groupName);
293                 try {
294                     Group group = man.getGroup(groupName);
295                     if (group == null) {
296                         log.error("Failed to resolve group {} for user {}.", groupName, name);
297                         continue;
298                     }
299                     Collection<String> subgroups = group.getGroups();
300                     // and recursively add more subgroups
301                     addSubgroups(allGroups, man, subgroups);
302                 } catch (AccessDeniedException e) {
303                     log.debug("Skipping denied group {} for user {}", groupName, getName(), e);
304                 } catch (UnsupportedOperationException e) {
305                     log.debug("Skipping unsupported  getGroup() for group {} and user {}", groupName, getName(), e);
306                 }
307 
308             }
309         }
310     }
311 
312     public String getRealm() {
313         return realm;
314     }
315 
316     /**
317      * Update the "last access" timestamp.
318      *
319      * @deprecated since 4.5, use {@link UserManager#updateLastAccessTimestamp(User)} instead
320      */
321     @Deprecated
322     public void setLastAccess() {
323         throw new UnsupportedOperationException("Use manager to update user details.");
324     }
325 
326     /**
327      * Not every user needs to have a node behind. Use manager to obtain nodes
328      *
329      * @deprecated since 4.5, use {@link UserManager#updateLastAccessTimestamp(User)} instead
330      */
331     @Deprecated
332     public Content getUserNode() {
333         throw new UnsupportedOperationException("Underlying storage node is no longer exposed nor required for custom user stores.");
334     }
335 
336     /**
337      * @deprecated since 4.5, use {@link UserManager} instead
338      */
339     @Override
340     @Deprecated
341     public void setProperty(String propertyName, String value) {
342         throw new UnsupportedOperationException("Use manager to modify properties of the user.");
343     }
344 
345     @Override
346     public String getIdentifier() {
347         return uuid;
348     }
349 
350     /**
351      * @deprecated since 4.5.1, use {@link MgnlUser#getIdentifier()} instead
352      */
353     @Deprecated
354     public String getUuid() {
355         return uuid;
356     }
357 
358     @Override
359     public String toString() {
360         return "MgnlUser - " + name + " [" + uuid + "]";
361     }
362 }