/*
* Copyright 2012 The Solmix Project
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.gnu.org/licenses/
* or see the FSF site: http://www.fsf.org.
*/
package org.solmix.fmk.jaas;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.apache.commons.lang.StringUtils;
import org.apache.karaf.jaas.modules.Encryption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.solmix.api.exception.SlxException;
import org.solmix.api.security.Realm;
import org.solmix.fmk.jaas.encryprtion.EncryptSupport;
/**
*
* @author Administrator
* @version 110035 2012-10-15
*/
public abstract class AbstractLoginModule implements LoginModule
{
protected Logger log = LoggerFactory.getLogger(getClass());
public static final String OPTION_REALM = "realm";
protected Subject subject;
protected CallbackHandler callbackHandler;
protected Map<String, Object> sharedState;
protected Map<String, Object> options;
protected Object realm;
protected boolean success;
protected String name;
protected char[] pswd;
public static final String STATUS = "statusValue";
public static final String GROUP_NAMES = "groupNames";
public static final String ROLE_NAMES = "roleNames";
public static final int STATUS_SUCCEEDED = 1;
public static final int STATUS_FAILED = 2;
public static final int STATUS_SKIPPED = 3;
public static final int STATUS_UNAVAILABLE = 4;
protected EncryptSupport encryptSupport;
protected SecurityAdminSupport securitySupport;
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject,
* javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
*/
@SuppressWarnings("unchecked")
@Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;
// don't overwrite group and roles set in the shared state
if (this.sharedState.get(GROUP_NAMES) == null) {
this.sharedState.put(GROUP_NAMES, new LinkedHashSet<String>());
}
if (this.sharedState.get(ROLE_NAMES) == null) {
this.sharedState.put(ROLE_NAMES, new LinkedHashSet<String>());
}
String realmName = (String) options.get(OPTION_REALM);
this.realm = StringUtils.isBlank(realmName) ? Realm.DEFAULT_REALM : Realm.Factory.newRealm(realmName);
encryptSupport = new EncryptSupport(options);
securitySupport = new SecurityAdminSupport(options);
}
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#login()
*/
@Override
public boolean login() throws LoginException {
if (this.callbackHandler == null) {
throw new LoginException("Error: no CallbackHandler available");
}
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("name");
callbacks[1] = new PasswordCallback("pswd", false);
// if the realm is not defined in the jaas configuration
// we ask use a callback to get the value
// if (this.useRealmCallback) {
// callbacks = (Callback[]) ArrayUtils.add(callbacks, new RealmCallback());
// }
this.success = false;
try {
this.callbackHandler.handle(callbacks);
this.name = ((NameCallback) callbacks[0]).getName();
this.pswd = ((PasswordCallback) callbacks[1]).getPassword();
this.validateUser();
} catch (IOException ioe) {
log.debug("Exception caught", ioe);
throw new LoginException(ioe.toString());
} catch (UnsupportedCallbackException ce) {
log.debug(ce.getMessage(), ce);
throw new LoginException(ce.getCallback().toString() + " not available");
} catch (SlxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// TODO: should not we set success BEFORE calling validateUser to give it chance to decide whether to throw an
// exception or reset the value to false?
this.success = true;
this.setSharedStatus(STATUS_SUCCEEDED);
return this.success;
}
/**
* Sets shared status value to be used by subsequent LoginModule(s).
* */
public void setSharedStatus(int status) {
this.sharedState.put(STATUS, new Integer(status));
}
public int getSharedStatus() {
Integer status = (Integer) this.sharedState.get(STATUS);
if (null != status) {
return status.intValue();
}
return STATUS_UNAVAILABLE;
}
public void setGroupNames(Set<String> names) {
this.getGroupNames().addAll(names);
}
public void addGroupName(String groupName) {
getGroupNames().add(groupName);
}
@SuppressWarnings("unchecked")
public Set<String> getGroupNames() {
return (Set<String>) this.sharedState.get(GROUP_NAMES);
}
public void setRoleNames(Set<String> names) {
this.getRoleNames().addAll(names);
}
public void addRoleName(String roleName) {
getRoleNames().add(roleName);
}
@SuppressWarnings("unchecked")
public Set<String> getRoleNames() {
return (Set<String>) this.sharedState.get(ROLE_NAMES);
}
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#commit()
*/
@Override
public boolean commit() throws LoginException {
/**
* If login module failed to authenticate then this method should simply return false instead of throwing an
* exception - refer to specs for more details
* */
if (!this.success) {
return false;
}
this.setEntity();
this.setACL();
return true;
}
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#abort()
*/
@Override
public boolean abort() throws LoginException {
return release();
}
/**
* {@inheritDoc}
*
* @see javax.security.auth.spi.LoginModule#logout()
*/
@Override
public boolean logout() throws LoginException {
return release();
}
public String getEncryptedPassword(String password) {
Encryption encryption = encryptSupport.getEncryption();
String encryptionPrefix = encryptSupport.getEncryptionPrefix();
String encryptionSuffix = encryptSupport.getEncryptionSuffix();
if (encryption == null) {
return password;
} else {
boolean prefix = encryptionPrefix == null || password.startsWith(encryptionPrefix);
boolean suffix = encryptionSuffix == null || password.endsWith(encryptionSuffix);
if (prefix && suffix) {
return password;
} else {
String p = encryption.encryptPassword(password);
if (encryptionPrefix != null) {
p = encryptionPrefix + p;
}
if (encryptionSuffix != null) {
p = p + encryptionSuffix;
}
return p;
}
}
}
public boolean checkPassword(String plain, String encrypted) {
Encryption encryption = encryptSupport.getEncryption();
String encryptionPrefix = encryptSupport.getEncryptionPrefix();
String encryptionSuffix = encryptSupport.getEncryptionSuffix();
if (encryption == null) {
return plain.equals(encrypted);
} else {
boolean prefix = encryptionPrefix == null || encrypted.startsWith(encryptionPrefix);
boolean suffix = encryptionSuffix == null || encrypted.endsWith(encryptionSuffix);
if (prefix && suffix) {
encrypted = encrypted.substring(encryptionPrefix != null ? encryptionPrefix.length() : 0, encrypted.length()
- (encryptionSuffix != null ? encryptionSuffix.length() : 0));
return encryption.checkPassword(plain, encrypted);
} else {
return plain.equals(encrypted);
}
}
}
/**
* Sets user details.
*/
public abstract void setEntity();
/**
* Sets access control list from the user, roles and groups.
*/
public abstract void setACL();
/**
* Checks if the credentials exist in the repository.
*
* @throws LoginException or specific subclasses to report failures.
* @throws SlxException
*/
public abstract void validateUser() throws LoginException, SlxException;
/**
* Releases all associated memory.subclass should override it.
*/
public boolean release() {
return true;
}
}