View Javadoc

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