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       * @param groupName the name of the group
100      * @return true if in group
101      */
102     @Override
103     public boolean inGroup(String groupName) {
104         log.debug("inGroup({})", groupName);
105         return SecuritySupport.Factory.getInstance().getUserManager(getRealm()).hasAny(getName(), groupName, NODE_GROUPS);
106     }
107 
108     /**
109      * Remove a group. Implementation is optional
110      */
111     @Override
112     public void removeGroup(String groupName) throws UnsupportedOperationException {
113         log.debug("removeGroup({})", groupName);
114         throw new UnsupportedOperationException("use manager to remove groups!");
115     }
116 
117     /**
118      * Adds this user to a group. Implementation is optional
119      * @param groupName group the user should be added to
120      */
121     @Override
122     public void addGroup(String groupName) throws UnsupportedOperationException {
123         log.debug("addGroup({})", groupName);
124         throw new UnsupportedOperationException("use manager to add groups!");
125     }
126 
127     @Override
128     public boolean isEnabled() {
129         log.debug("isEnabled()");
130         return enabled ;
131     }
132 
133     /**
134      * This methods sets flag just on the bean. It does not update persisted user data. Use manager to update user data.
135      * @deprecated since 4.5, use {@link UserManager#setProperty(User, String, String)} instead
136      */
137     @Override
138     @Deprecated
139     public void setEnabled(boolean enabled) {
140         log.debug("setEnabled({})", enabled);
141         throw new UnsupportedOperationException("use manager to enable user!");
142     }
143 
144     /**
145      * Is this user in a specified role?
146      * @param roleName the name of the role
147      * @return true if in role
148      */
149     @Override
150     public boolean hasRole(String roleName) {
151         return SecuritySupport.Factory.getInstance().getUserManager(getRealm()).hasAny(getName(), roleName, NODE_ROLES);
152     }
153 
154     @Override
155     public void removeRole(String roleName) throws UnsupportedOperationException {
156         log.debug("removeRole({})", roleName);
157         throw new UnsupportedOperationException("use manager to remove roles!");
158     }
159 
160     @Override
161     public void addRole(String roleName) throws UnsupportedOperationException {
162         log.debug("addRole({})", roleName);
163         throw new UnsupportedOperationException("use manager to add roles!");
164     }
165 
166     public int getFailedLoginAttempts(){
167         try{
168             return Integer.valueOf(this.properties.get("failedLoginAttempts"));
169         }catch(Exception e){
170             return 0;
171         }
172     }
173 
174     public Calendar getReleaseTime(){
175         try{
176             return ISO8601.parse(this.properties.get("releaseTime"));
177         }catch(Exception e){
178             return null;
179         }
180     }
181 
182     @Override
183     public String getName() {
184         log.debug("getName()=>{}", name);
185         return name;
186     }
187 
188     @Override
189     public String getPassword() {
190         return encodedPassword;
191     }
192 
193     @Deprecated
194     /**
195      * @deprecated Since 4.5.8. Password is now encoded by BCrypt and therefore cannot be decoded.
196      */
197     protected String decodePassword(String encodedPassword) {
198         throw new UnsupportedOperationException();
199     }
200 
201     @Override
202     public String getLanguage() {
203         log.debug("getLang()=>{}", language);
204         return this.language;
205     }
206 
207     @Override
208     public String getProperty(String propertyName) {
209         log.debug("getProperty({})", propertyName);
210         return properties.get(propertyName);
211     }
212 
213     @Override
214     public Collection<String> getGroups() {
215         log.debug("getGroups()");
216         return groups;
217     }
218 
219     @Override
220     public Collection<String> getAllGroups() {
221         // TODO: if the user is just a simple bean, then this method doesn't belong here anymore!!!!
222         // should be moved to user manager or to group manager???
223         log.debug("get groups for {}", getName());
224 
225         final Set<String> allGroups = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
226         final Collection<String> groups = getGroups();
227 
228         // FYI: can't initialize upfront as the instance of the user class needs to be created BEFORE repo is ready
229         GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager();
230 
231         // add all subbroups
232         addSubgroups(allGroups, man, groups);
233 
234         return allGroups;
235     }
236 
237     @Override
238     public Collection<String> getRoles() {
239         log.debug("getRoles()");
240         return roles;
241     }
242 
243     @Override
244     public Collection<String> getAllRoles() {
245         // TODO: if the user is just a simple bean, then this method doesn't belong here anymore!!!!
246         log.debug("get roles for {}", getName());
247 
248         final Set<String> allRoles = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
249         final Collection<String> roles = getRoles();
250 
251         // add all direct user groups
252         allRoles.addAll(roles);
253 
254         Collection<String> allGroups = getAllGroups();
255 
256         // FYI: can't initialize upfront as the instance of the user class needs to be created BEFORE repo is ready
257         GroupManager man = SecuritySupport.Factory.getInstance().getGroupManager();
258 
259         // add roles from all groups
260         for (String group : allGroups) {
261             try {
262                 allRoles.addAll(man.getGroup(group).getRoles());
263             } catch (AccessDeniedException e) {
264                 log.debug("Skipping denied group {} for user {}", group, getName(), e);
265             } catch (UnsupportedOperationException e) {
266                 log.debug("Skipping unsupported  getGroup() for group {} and user {}", group, getName(), e);
267             }
268         }
269         return allRoles;
270     }
271 
272     public String getPath() {
273         return this.path;
274     }
275 
276     @Deprecated
277     public void setPath(String path) {
278         this.path = path;
279     }
280 
281     /**
282      * 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.
283      */
284     private void addSubgroups(final Set<String> allGroups, GroupManager man, Collection<String> groups) {
285         for (String groupName : groups) {
286             // check if this group was not already added to prevent infinite loops
287             if (!allGroups.contains(groupName)) {
288                 allGroups.add(groupName);
289                 try {
290                     Group group = man.getGroup(groupName);
291                     if (group == null) {
292                         log.error("Failed to resolve group {} for user {}.", groupName, name);
293                         continue;
294                     }
295                     Collection<String> subgroups = group.getGroups();
296                     // and recursively add more subgroups
297                     addSubgroups(allGroups, man, subgroups);
298                 } catch (AccessDeniedException e) {
299                     log.debug("Skipping denied group {} for user {}", groupName, getName(), e);
300                 } catch (UnsupportedOperationException e) {
301                     log.debug("Skipping unsupported  getGroup() for group {} and user {}", groupName, getName(), e);
302                 }
303 
304             }
305         }
306     }
307 
308     public String getRealm() {
309         return realm;
310     }
311 
312     /**
313      * Update the "last access" timestamp.
314      * @deprecated since 4.5, use {@link UserManager#updateLastAccessTimestamp(User)} instead
315      */
316     @Deprecated
317     public void setLastAccess() {
318         throw new UnsupportedOperationException("Use manager to update user details.");
319     }
320 
321     /**
322      * Not every user needs to have a node behind. Use manager to obtain nodes
323      * @deprecated since 4.5, use {@link UserManager#updateLastAccessTimestamp(User)} instead
324      */
325     @Deprecated
326     public Content getUserNode() {
327         throw new UnsupportedOperationException("Underlying storage node is no longer exposed nor required for custom user stores.");
328     }
329 
330     /**
331      * @deprecated since 4.5, use {@link UserManager} instead
332      */
333     @Override
334     @Deprecated
335     public void setProperty(String propertyName, String value) {
336         throw new UnsupportedOperationException("Use manager to modify properties of the user.");
337     }
338 
339     @Override
340     public String getIdentifier() {
341         return uuid;
342     }
343 
344     /**
345      * @deprecated since 4.5.1, use {@link MgnlUser#getIdentifier()} instead
346      */
347     @Deprecated
348     public String getUuid() {
349         return uuid;
350     }
351 
352     @Override
353     public String toString() {
354         return "MgnlUser - " + name + " [" + uuid + "]";
355     }
356 }