/**
* 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.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.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
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.core.security.UserPrincipal;
import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
/**
* Authentication module implementation using JCR to retrieve the users.
* @version $Id: MagnoliaAuthenticationModule.java 45632 2011-05-28 12:46:32Z had $
*/
// JR requires login module to be serializable!
public class MagnoliaAuthenticationModule extends AbstractLoginModule implements UserAwareLoginModule, Serializable {
private static final boolean logAdmin = false;
protected User user;
/**
* As silly as it seems this class sole purpose of existence is to implement Serializable required by JR itself.
* @author had
* @version $Id: JCRAuthenticationModule.java 45632 2011-05-28 12:46:32Z had $
*/
public class MagnoliaJRAdminPrincipal extends AdminPrincipal implements Principal, Serializable {
public MagnoliaJRAdminPrincipal(String name) {
super(name);
}
}
/**
* 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.");
}
matchPassword();
if (!this.user.isEnabled()) {
throw new AccountLockedException("User account " + this.name + " is locked.");
}
if (!UserManager.ANONYMOUS_USER.equals(user.getName()) && !isAdmin()) {
// 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.
if (logAdmin || !"admin".equals(name)) {
log.debug("getting user manager for realm " + realm.getName());
}
return SecuritySupport.Factory.getInstance().getUserManager(realm.getName());
}
protected void initUser() throws LoginException {
if (logAdmin || !"admin".equals(name)) {
log.debug("initializing user {}", name);
}
// we do not verify admin user, against our user base (which is in repo we are trying to access with this user), but we still check the password.
// This user has assigned no privileges, just dummy near empty user object (with name and pwd only).
if (isAdmin()) {
Map<String, String> props = new HashMap<String, String>();
//TODO: double check if we need to reset pwd here or if getAdminSession() is enough!!!
props.put(MgnlUserManager.PROPERTY_PASSWORD, new String(Base64.encodeBase64("admin".getBytes())));
MgnlUser user = new MgnlUser(name, null, Collections.EMPTY_LIST, Collections.EMPTY_LIST,props);
this.user = user;
// admin user is used by system and have no access to regular user stuff
return;
}
long start = System.currentTimeMillis();
this.user = getUserManager().getUser(name);
if (logAdmin || !"admin".equals(name)) {
log.debug("initialized user {} in {}ms", name, (System.currentTimeMillis() - start));
}
}
protected void matchPassword() throws LoginException {
String serverPassword = user.getPassword();
if (StringUtils.isEmpty(serverPassword)) {
throw new FailedLoginException("we do not allow users with no password");
}
if (!StringUtils.equals(serverPassword, new String(this.pswd))) {
throw new FailedLoginException("passwords do not match");
}
}
/**
* Set user details.
*/
@Override
public void setEntity() {
if (isAdmin()) {
// JR specific principal for repo only authentication. This part will be probably different per repo.
this.subject.getPrincipals().add(new MagnoliaJRAdminPrincipal(name));
// admin user is used by system and have no access to regular user stuff
return;
} else if ("superuser".equals(name)) {
// add admin principal also for our superuser to make sure system doesn't deny him any access
this.subject.getPrincipals().add(new MagnoliaJRAdminPrincipal(name));
} else {
// TODO: make JR dependency pluggable!!!
// and ordinary JR User principal for everybody else
this.subject.getPrincipals().add(new UserPrincipal(name));
}
// our user has our principal!!!!
this.subject.getPrincipals().add(this.user);
this.subject.getPrincipals().add(this.realm);
collectGroupNames();
collectRoleNames();
}
private boolean isAdmin() {
// TODO: make admin user name configurable (read from properties)
return this.name != null && this.name.equals("admin");
}
/**
* 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;
}
}