View Javadoc

1   /**
2    * This file Copyright (c) 2003-2011 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.jaas.sp.jcr;
35  
36  import info.magnolia.cms.security.MgnlUser;
37  import info.magnolia.cms.security.MgnlUserManager;
38  import info.magnolia.cms.security.SecuritySupport;
39  import info.magnolia.cms.security.User;
40  import info.magnolia.cms.security.UserManager;
41  import info.magnolia.jaas.sp.AbstractLoginModule;
42  import info.magnolia.jaas.sp.UserAwareLoginModule;
43  
44  import java.io.Serializable;
45  import java.util.Calendar;
46  import java.util.GregorianCalendar;
47  import java.util.TimeZone;
48  
49  import javax.security.auth.login.AccountLockedException;
50  import javax.security.auth.login.AccountNotFoundException;
51  import javax.security.auth.login.FailedLoginException;
52  import javax.security.auth.login.LoginException;
53  
54  import org.apache.commons.lang.StringUtils;
55  import org.apache.jackrabbit.value.ValueFactoryImpl;
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  
59  /**
60   * Authentication module implementation using JCR to retrieve the users.
61   * @author Sameer Charles $Id: JCRAuthenticationModule.java 51822 2011-11-22 13:37:32Z ochytil $
62   */
63  public class JCRAuthenticationModule extends AbstractLoginModule implements UserAwareLoginModule, Serializable {
64  
65      private static final Logger log = LoggerFactory.getLogger(JCRAuthenticationModule.class);
66      protected User user;
67  
68      /**
69       * Get number of failed login attempts before locking account.
70       */
71      public int getMaxAttempts() {
72          String realm;
73          if (this.user instanceof MgnlUser) {
74              realm = ((MgnlUser) user).getRealm();
75              //If not supported by user manager then lockout is disabled.
76          } else {
77              return 0;
78          }
79          MgnlUserManager manager = (MgnlUserManager) SecuritySupport.Factory.getInstance().getUserManager(realm);
80          return manager.getMaxFailedLoginAttempts();
81      }
82  
83      /**
84       * Get time period for time lockout.
85       */
86      public long getTimeLock(){
87          String realm;
88          if (this.user instanceof MgnlUser) {
89              realm = ((MgnlUser) user).getRealm();
90          } else {
91              //If not supported by user manager then lockout is disabled.
92              return 0;
93          }
94          MgnlUserManager manager = (MgnlUserManager) SecuritySupport.Factory.getInstance().getUserManager(realm);
95          return manager.getLockTimePeriod();
96      }
97  
98      /**
99  
100     /**
101      * Checks is the credentials exist in the repository.
102      * @throws LoginException or specific subclasses (which will be handled further for user feedback)
103      */
104     @Override
105     public void validateUser() throws LoginException {
106         initUser();
107 
108         if (this.user == null) {
109             throw new AccountNotFoundException("User account " + this.name + " not found.");
110         }
111 
112         if (!this.user.isEnabled()) {
113             throw new AccountLockedException("User account " + this.name + " is locked.");
114         }
115 
116         matchPassword();
117 
118         if (!UserManager.ANONYMOUS_USER.equals(user.getName())) {
119             // update last access date for all non anonymous users
120             getUserManager().updateLastAccessTimestamp(user);
121         }
122     }
123 
124     private UserManager getUserManager() {
125         // can't get the factory upfront and can't use IoC as this class is instantiated by JCR/JAAS before anything else is ready.
126         log.debug("getting user manager for realm " + realm.getName());
127         return SecuritySupport.Factory.getInstance().getUserManager(realm.getName());
128     }
129 
130 
131 
132     protected void initUser() throws LoginException {
133         log.debug("initializing user {}", name);
134 
135         long start = System.currentTimeMillis();
136         this.user = getUserManager().getUser(name);
137         log.debug("initialized user {} in {}ms", name, (System.currentTimeMillis() - start));
138     }
139 
140     protected void matchPassword() throws LoginException {
141         if(getMaxAttempts() > 0 && !UserManager.ANONYMOUS_USER.equals(user.getName()) && getTimeLock() > 0){
142             //Only MgnlUser is able to use lockout for time period like hard-lock (timeLock is higher than 0).
143             Calendar currentTime = new GregorianCalendar(TimeZone.getDefault());
144             Calendar lockTime = new GregorianCalendar(TimeZone.getDefault());
145             MgnlUser mgnlUser = (MgnlUser) user;
146             if(mgnlUser.getReleaseTime() != null){
147                 lockTime.clear();
148                 lockTime.setTime(mgnlUser.getReleaseTime().getTime());
149             }
150             if(lockTime.after(currentTime) && mgnlUser.getReleaseTime() != null){
151                 throw new LoginException("User account " + this.name + " is locked until " + mgnlUser.getReleaseTime().getTime() + ".");
152             }
153         }
154         String serverPassword = user.getPassword();
155 
156         if (StringUtils.isEmpty(serverPassword)) {
157             throw new FailedLoginException("we do not allow users with no password");
158         }
159 
160         if (!StringUtils.equals(serverPassword, new String(this.pswd))) {
161             if (getMaxAttempts() > 0 && !UserManager.ANONYMOUS_USER.equals(user.getName())){
162                 //Only MgnlUser is able to use lockout i.e. has maxAttempts higher than 0.
163                 UserManager userManager = getUserManager();
164                 MgnlUser mgnlUser = (MgnlUser) user;
165                 userManager.setProperty(mgnlUser, "failedLoginAttempts", ValueFactoryImpl.getInstance().createValue((mgnlUser.getFailedLoginAttempts() + 1)));
166 
167                 //Hard lock
168                 if (mgnlUser.getFailedLoginAttempts() >= getMaxAttempts() && getTimeLock() <= 0){
169                     userManager.setProperty(mgnlUser, "enabled", ValueFactoryImpl.getInstance().createValue(false));
170                     userManager.setProperty(mgnlUser, "failedLoginAttempts", ValueFactoryImpl.getInstance().createValue(0));
171                     log.warn("Account " + this.name + " was locked due to high number of failed login attempts.");
172 
173                     //Lock for time period
174                 }else if (mgnlUser.getFailedLoginAttempts() >= getMaxAttempts() && getTimeLock() > 0){
175                     userManager.setProperty(mgnlUser, "failedLoginAttempts", ValueFactoryImpl.getInstance().createValue(0));
176                     Calendar calendar = new GregorianCalendar(TimeZone.getDefault());
177                     calendar.add(Calendar.MINUTE, (int)getTimeLock());
178                     userManager.setProperty(mgnlUser, "releaseTime", ValueFactoryImpl.getInstance().createValue(calendar));
179                     log.warn("Account " + this.name + " was locked for " + getTimeLock() + " minute(s) due to high number of failed login attempts.");
180                 }
181                 throw new FailedLoginException("passwords do not match");
182             }
183             if(user instanceof MgnlUser){
184                 MgnlUser mgnlUser = (MgnlUser) user;
185                 UserManager userManager = getUserManager();
186                 if (getMaxAttempts() > 0 && !UserManager.ANONYMOUS_USER.equals(mgnlUser.getName()) && mgnlUser.getFailedLoginAttempts() > 0){
187                     userManager.setProperty(mgnlUser, "failedLoginAttempts", ValueFactoryImpl.getInstance().createValue(0));
188                 }
189             }
190         }
191     }
192 
193     /**
194      * Set user details.
195      */
196     @Override
197     public void setEntity() {
198 
199         this.subject.getPrincipals().add(this.user);
200         this.subject.getPrincipals().add(this.realm);
201 
202         collectGroupNames();
203         collectRoleNames();
204     }
205 
206     /**
207      * Set access control list from the user, roles and groups.
208      */
209     @Override
210     public void setACL() {
211     }
212 
213     /**
214      * Extract all the configured roles from the given node. (which can be the user node or a group node)
215      */
216     public void collectRoleNames() {
217         for (String role : this.user.getAllRoles()) {
218             addRoleName(role);
219         }
220     }
221 
222     /**
223      * Extract all the configured groups from the given node. (which can be the user node or a group node)
224      */
225     public void collectGroupNames() {
226         for (String group : this.user.getAllGroups()) {
227             addGroupName(group);
228         }
229     }
230 
231     @Override
232     public User getUser() {
233         return user;
234     }
235 }