/** * This file Copyright (c) 2003-2012 Magnolia International * Ltd. (http://www.magnolia-cms.com). All rights reserved. * * * This file is dual-licensed under both the Magnolia * Network Agreement and the GNU General Public License. * You may elect to use one or the other of these licenses. * * This file is distributed in the hope that it will be * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT. * Redistribution, except as permitted by whichever of the GPL * or MNA you select, is prohibited. * * 1. For the GPL license (GPL), you can redistribute and/or * modify this file under the terms of the GNU General * Public License, Version 3, as published by the Free Software * Foundation. You should have received a copy of the GNU * General Public License, Version 3 along with this program; * if not, write to the Free Software Foundation, Inc., 51 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * 2. For the Magnolia Network Agreement (MNA), this file * and the accompanying materials are made available under the * terms of the MNA which accompanies this distribution, and * is available at http://www.magnolia-cms.com/mna.html * * Any modifications to this file must keep this entire header * intact. * */ package info.magnolia.jaas.sp.jcr; import info.magnolia.cms.security.SecurityUtil; import info.magnolia.cms.security.MgnlUser; import info.magnolia.cms.security.MgnlUserManager; import info.magnolia.cms.security.SecuritySupport; import info.magnolia.cms.security.User; import info.magnolia.cms.security.UserManager; import info.magnolia.jaas.sp.AbstractLoginModule; import info.magnolia.jaas.sp.UserAwareLoginModule; import java.io.Serializable; import java.util.Arrays; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.TimeZone; import javax.security.auth.login.AccountLockedException; import javax.security.auth.login.AccountNotFoundException; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.value.ValueFactoryImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Authentication module implementation using JCR to retrieve the users. * @version $Id$ */ public class JCRAuthenticationModule extends AbstractLoginModule implements UserAwareLoginModule, Serializable { private static final Logger log = LoggerFactory.getLogger(JCRAuthenticationModule.class); protected User user; /** * Get number of failed login attempts before locking account. */ public int getMaxAttempts() { String realm; if (this.user instanceof MgnlUser) { realm = ((MgnlUser) user).getRealm(); //If not supported by user manager then lockout is disabled. } else { return 0; } MgnlUserManager manager = (MgnlUserManager) SecuritySupport.Factory.getInstance().getUserManager(realm); return manager.getMaxFailedLoginAttempts(); } /** * Get time period for time lockout. */ public long getTimeLock(){ String realm; if (this.user instanceof MgnlUser) { realm = ((MgnlUser) user).getRealm(); } else { //If not supported by user manager then lockout is disabled. return 0; } MgnlUserManager manager = (MgnlUserManager) SecuritySupport.Factory.getInstance().getUserManager(realm); return manager.getLockTimePeriod(); } /** /** * Checks is the credentials exist in the repository. * @throws LoginException or specific subclasses (which will be handled further for user feedback) */ @Override public void validateUser() throws LoginException { initUser(); if (this.user == null) { throw new AccountNotFoundException("User account " + this.name + " not found."); } if (!this.user.isEnabled()) { throw new AccountLockedException("User account " + this.name + " is locked."); } matchPassword(); if (!UserManager.ANONYMOUS_USER.equals(user.getName())) { // update last access date for all non anonymous users getUserManager().updateLastAccessTimestamp(user); } } private UserManager getUserManager() { // can't get the factory upfront and can't use IoC as this class is instantiated by JCR/JAAS before anything else is ready. log.debug("getting user manager for realm " + realm.getName()); return SecuritySupport.Factory.getInstance().getUserManager(realm.getName()); } protected void initUser() throws LoginException { log.debug("initializing user {}", name); long start = System.currentTimeMillis(); this.user = getUserManager().getUser(name); log.debug("initialized user {} in {}ms", name, (System.currentTimeMillis() - start)); } protected void matchPassword() throws LoginException { if(getMaxAttempts() > 0 && !UserManager.ANONYMOUS_USER.equals(user.getName()) && getTimeLock() > 0){ //Only MgnlUser is able to use lockout for time period like hard-lock (timeLock is higher than 0). Calendar currentTime = new GregorianCalendar(TimeZone.getDefault()); Calendar lockTime = new GregorianCalendar(TimeZone.getDefault()); MgnlUser mgnlUser = (MgnlUser) user; if(mgnlUser.getReleaseTime() != null){ lockTime.clear(); lockTime.setTime(mgnlUser.getReleaseTime().getTime()); } if(lockTime.after(currentTime) && mgnlUser.getReleaseTime() != null){ throw new LoginException("User account " + this.name + " is locked until " + mgnlUser.getReleaseTime().getTime() + "."); } } String serverPassword = user.getPassword(); if (StringUtils.isEmpty(serverPassword)) { throw new FailedLoginException("Magnolia CMS does not allow login to users with no password."); } boolean match = false; if (Base64.isArrayByteBase64(serverPassword.getBytes())) { match = Arrays.equals(Base64.decodeBase64(serverPassword), new String(this.pswd).getBytes()); } else { match = SecurityUtil.matchBCrypted(new String(this.pswd), serverPassword); } if (!match) { if (getMaxAttempts() > 0 && !UserManager.ANONYMOUS_USER.equals(user.getName())){ //Only MgnlUser is able to use lockout i.e. has maxAttempts higher than 0. UserManager userManager = getUserManager(); MgnlUser mgnlUser = (MgnlUser) user; userManager.setProperty(mgnlUser, "failedLoginAttempts", ValueFactoryImpl.getInstance().createValue((mgnlUser.getFailedLoginAttempts() + 1))); //Hard lock if (mgnlUser.getFailedLoginAttempts() >= getMaxAttempts() && getTimeLock() <= 0){ userManager.setProperty(mgnlUser, "enabled", ValueFactoryImpl.getInstance().createValue(false)); userManager.setProperty(mgnlUser, "failedLoginAttempts", ValueFactoryImpl.getInstance().createValue(0)); log.warn("Account " + this.name + " was locked due to high number of failed login attempts."); //Lock for time period }else if (mgnlUser.getFailedLoginAttempts() >= getMaxAttempts() && getTimeLock() > 0){ userManager.setProperty(mgnlUser, "failedLoginAttempts", ValueFactoryImpl.getInstance().createValue(0)); Calendar calendar = new GregorianCalendar(TimeZone.getDefault()); calendar.add(Calendar.MINUTE, (int)getTimeLock()); userManager.setProperty(mgnlUser, "releaseTime", ValueFactoryImpl.getInstance().createValue(calendar)); log.warn("Account " + this.name + " was locked for " + getTimeLock() + " minute(s) due to high number of failed login attempts."); } } throw new FailedLoginException("Passwords do not match"); } if(user instanceof MgnlUser){ MgnlUser mgnlUser = (MgnlUser) user; UserManager userManager = getUserManager(); if (getMaxAttempts() > 0 && !UserManager.ANONYMOUS_USER.equals(mgnlUser.getName()) && mgnlUser.getFailedLoginAttempts() > 0){ userManager.setProperty(mgnlUser, "failedLoginAttempts", ValueFactoryImpl.getInstance().createValue(0)); } } } /** * Set user details. */ @Override public void setEntity() { this.subject.getPrincipals().add(this.user); this.subject.getPrincipals().add(this.realm); collectGroupNames(); collectRoleNames(); } /** * Set access control list from the user, roles and groups. */ @Override public void setACL() { } /** * Extract all the configured roles from the given node. (which can be the user node or a group node) */ public void collectRoleNames() { for (String role : this.user.getAllRoles()) { addRoleName(role); } } /** * Extract all the configured groups from the given node. (which can be the user node or a group node) */ public void collectGroupNames() { for (String group : this.user.getAllGroups()) { addGroupName(group); } } @Override public User getUser() { return user; } }