User: oleg
Date: 01/01/09 17:24:12
Added: src/main/org/jboss/security/plugins
AbstractServerLoginModule.java
DatabaseServerLoginModule.java
JaasSecurityManager.java
JaasSecurityManagerService.java
JaasSecurityManagerServiceMBean.java package.html
Log:
Package structure for security stuff improved.
Classes from "system" package moved to "security" package.
Added "security/plugins" and "security/plugins/samples" packages.
Added JaasServerLoginModule and AbstractServerLoginModule classes
by Edward Kenworthy <[EMAIL PROTECTED]>
(file based implementation for JAAS security).
Revision Changes Path
1.1
jboss/src/main/org/jboss/security/plugins/AbstractServerLoginModule.java
Index: AbstractServerLoginModule.java
===================================================================
/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.security.plugins;
import java.util.*;
import java.io.*;
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.login.FailedLoginException;
import javax.security.auth.spi.LoginModule;
/**
* AbstractServerLoginModule
* written by: Edward Kenworthy 12th Dec 2000
*
* This class implements the common functionality required for a JAAS
ServerLoginModule.
* To implement your own implementation you need to add just the user/password/roles
lookup
* functionality.
*
* It attaches the roles to the subject as public credentials.
* According to the JAAS spec it requires privileged access to do this, and as I
don't
* explicilty code that privilege I assume I must have it by virtue of being a
ServerLoginModule.
*
* As a minimum you must implement:
*
* protected String getUsersRoles(); // returns a csv list of the users roles
* protected String getUsersPassword(); // returns the users password
*
* You may also wish to override
*
* public void initialize(Subject subject, CallbackHandler callbackHandler, Map
sharedState, Map options)
*
* In which case the first line of your initialize() method should be
super.initialize(subject, callbackHandler, sharedState, options);
*
* You may also wish to override
*
* public boolean login() throws LoginException
*
* In which case the last line of your login() method should be return super.login();
*
* @author <a href="[EMAIL PROTECTED]">Edward Kenworthy</a>
*/
public abstract class AbstractServerLoginModule implements LoginModule
{
private Subject _subject;
private CallbackHandler _callbackHandler;
// username and password
private String _username;
protected String getUsername() {return _username;}
private char[] _password;
abstract protected Enumeration getUsersRoles();
abstract protected String getUsersPassword();
public void initialize(Subject subject, CallbackHandler callbackHandler, Map
sharedState, Map options)
{
_subject = subject;
_callbackHandler = callbackHandler;
}
public boolean login() throws LoginException
{
Callback[] callbacks = new Callback[2];
// prompt for a username and password
if (_callbackHandler == null)
{
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the
user");
}
callbacks[0] = new NameCallback("User name: ", "guest");
callbacks[1] = new PasswordCallback("Password: ", false);
try
{
_callbackHandler.handle(callbacks);
_username = ((NameCallback)callbacks[0]).getName();
char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
if (tmpPassword != null)
{
_password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, _password, 0, tmpPassword.length);
((PasswordCallback)callbacks[1]).clearPassword();
}
}
catch (java.io.IOException ioe)
{
throw new LoginException(ioe.toString());
}
catch (UnsupportedCallbackException uce)
{
throw new LoginException("Error: " + uce.getCallback().toString() +
" not available to garner authentication information " +
"from the user");
}
String userPassword = getUsersPassword();
if (_password == null || userPassword == null || !(new
String(_password)).equals(userPassword))
{
System.out.print("[JAASSecurity] Bad password.\n");
throw new FailedLoginException("Password Incorrect/Password Required");
}
System.out.print("[JAASSecurity] User '" + _username + "' authenticated.\n");
return true;
}
/**
* Method to commit the authentication process (phase 2).
*/
public boolean commit() throws LoginException
{
Set roles = _subject.getPublicCredentials();
Enumeration roleList = getUsersRoles();
if (roleList != null) {
while (roleList.hasMoreElements()) {
roles.add(roleList.nextElement());
}
}
return true;
}
/**
* Method to abort the authentication process (phase 2).
*/
public boolean abort() throws LoginException
{
_username = null;
if (_password != null)
{
for (int i = 0; i < _password.length; i++)
_password[i] = ' ';
_password = null;
}
return true;
}
public boolean logout() throws LoginException
{
return true;
}
}
1.1
jboss/src/main/org/jboss/security/plugins/DatabaseServerLoginModule.java
Index: DatabaseServerLoginModule.java
===================================================================
/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.security.plugins;
import java.util.Map;
import java.util.Set;
import java.util.Arrays;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
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.login.FailedLoginException;
import javax.security.auth.spi.LoginModule;
public class DatabaseServerLoginModule implements LoginModule {
private String _db;
private String _table;
private String _nameCol;
private String _pswCol;
private Subject _subject;
private CallbackHandler _callbackHandler;
private String _username;
/**
* Initialize this LoginModule.
*/
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options) {
_subject = subject;
_callbackHandler = callbackHandler;
_db = (String) options.get("db");
_table = (String) options.get("table");
_nameCol = (String) options.get("name");
_pswCol = (String) options.get("password");
}
/**
* Method to authenticate a Subject (phase 1).
*/
public boolean login() throws LoginException {
Callback[] callbacks = new Callback[2];
char[] password;
char[] tmpPassword;
InitialContext initial;
DataSource ds;
Connection conn = null;
PreparedStatement ps;
ResultSet rs;
Object psw;
boolean ok;
try {
// prompt for a username and password
if (_callbackHandler == null) {
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from
the user");
}
callbacks[0] = new NameCallback("User name: ", "guest");
callbacks[1] = new PasswordCallback("Password: ", false);
_callbackHandler.handle(callbacks);
_username = ((NameCallback)callbacks[0]).getName();
tmpPassword = ((PasswordCallback)callbacks[1]).getPassword();
if (tmpPassword == null) {
password = null;
} else {
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
((PasswordCallback)callbacks[1]).clearPassword();
}
// password authorization
if (_pswCol != null) {
initial = new InitialContext();
ds = (DataSource) initial.lookup( _db );
conn = ds.getConnection();
ps = conn.prepareStatement("SELECT " + _pswCol + " FROM " + _table +
" WHERE " + _nameCol + "=?");
ps.setString(1, _username);
rs = ps.executeQuery();
if (!rs.next()) {
throw new FailedLoginException("Incorrect user name");
}
psw = rs.getObject(1);
if (password == null || psw == null) {
ok = (password == psw);
} else if (psw instanceof byte[]) {
byte[] bpsw;
int len;
char[] cpsw;
bpsw = (byte[]) psw;
// trim zero bytes
for (len = bpsw.length; len>0; len--) {
if (bpsw[len - 1] != 0) {
break;
}
}
cpsw = new char[len];
for (int i = 0; i < len; i++) {
cpsw[i] = (char) bpsw[i];
}
ok = Arrays.equals(password, cpsw);
} else if (psw instanceof String) {
// trim spaces
ok = (new String(password)).equals(((String) psw).trim());
} else {
throw new LoginException("Unsupported SQL type of password
column");
}
if (!ok) {
throw new FailedLoginException("Incorrect password");
}
}
} catch (NamingException ex) {
throw new LoginException(ex.toString());
} catch (java.io.IOException ex) {
throw new LoginException(ex.toString());
} catch (SQLException ex) {
throw new LoginException(ex.toString());
} catch (UnsupportedCallbackException uce) {
throw new LoginException("Error: " + uce.getCallback().toString() +
" not available to garner authentication information " +
"from the user");
} finally {
if (conn != null) {
try {
conn.close();
} catch (Exception ex) {
}
}
}
return true;
}
/**
* Method to commit the authentication process (phase 2).
*/
public boolean commit() throws LoginException {
return true;
}
/**
* Method to abort the authentication process (phase 2).
*/
public boolean abort() throws LoginException {
_username = null;
return true;
}
public boolean logout() throws LoginException {
return true;
}
}
1.1
jboss/src/main/org/jboss/security/plugins/JaasSecurityManager.java
Index: JaasSecurityManager.java
===================================================================
/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.security.plugins;
import java.io.File;
import java.io.Serializable;
import java.net.URL;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.util.Set;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Iterator;
import java.security.Principal;
import javax.security.auth.Subject;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import com.sun.security.auth.login.ConfigFile;
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.Reference;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.transaction.TransactionManager;
import org.jboss.logging.Log;
import org.jboss.util.ServiceMBeanSupport;
import org.jboss.security.EJBSecurityManager;
import org.jboss.security.RealmMapping;
/**
* The JaasSecurityManager is responsible both for validating credentials
* associated with principals and for role mapping.
*
* @author <a href="[EMAIL PROTECTED]">Oleg Nitz</a>
*/
public class JaasSecurityManager
implements EJBSecurityManager, RealmMapping, Serializable {
/**
* Security manager name.
*/
private final String _smName;
/**
* Maps an original principal to authenticated client credential, aka password.
* Should be a time or size limited cache for better scalability.
* This is a master cache, when a principal is removed from this cache,
* the related entries from all other caches should be removed too.
*/
private final HashMap _passwords = new HashMap();
/**
* Maps original principal to principal for the bean.
*/
private final HashMap _principals = new HashMap();
/**
* Maps original principal to Set of roles for the bean.
*/
private final HashMap _roles = new HashMap();
/**
* @param smName The name of the security manager
*/
public JaasSecurityManager(String smName) {
_smName = smName;
}
public boolean isValid(Principal principal, Object credential) {
boolean ok;
char[] authenticated;
authenticated = (char[]) _passwords.get(principal);
if (authenticated == null) {
return authenticate(_smName, principal, credential);
} else {
if ((credential instanceof char[]) &&
Arrays.equals(authenticated, (char[]) credential)) {
return true;
} else {
// the password may have changed - reauthenticate
return authenticate(_smName, principal, credential);
}
}
}
public Principal getPrincipal(Principal principal) {
Principal result;
result = (Principal) _principals.get(principal);
if (result == null) {
if (authenticate(_smName, principal, null)) {
result = (Principal) _principals.get(principal);
}
}
return result;
}
public boolean doesUserHaveRole(Principal principal, Set roleNames)
{
Set roles;
Iterator it;
if (roleNames == null)
return true;
roles = (Set) _roles.get(principal);
if (roles == null) {
if (!authenticate(_smName, principal, null)) {
return false;
}
}
it = roleNames.iterator();
while (it.hasNext()) {
if (roles.contains(it.next())) {
return true;
}
}
return false;
}
/**
* @param bean The bean name
* @param userName The user name
* @param password The password
* @return false on failure, true on success.
*/
private boolean authenticate(String beanName, Principal principal, Object
credential) {
LoginContext lc;
Subject subj;
final String userName = principal.getName();
final char[] password = (char[]) credential;
Iterator it;
Principal beanPrincipal;
try {
lc = new LoginContext(beanName, new CallbackHandler() {
public void handle(Callback[] callbacks) throws
UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName(userName);
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword(password);
} else {
throw new UnsupportedCallbackException(callbacks[i],
"Unrecognized Callback");
}
}
}
});
lc.login();
_passwords.put(principal, password);
subj = lc.getSubject();
beanPrincipal = principal;
it = subj.getPrincipals().iterator();
if (it.hasNext()) {
beanPrincipal = (Principal) it.next();
}
_principals.put(principal, beanPrincipal);
_roles.put(principal, subj.getPublicCredentials());
return true;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
}
1.1
jboss/src/main/org/jboss/security/plugins/JaasSecurityManagerService.java
Index: JaasSecurityManagerService.java
===================================================================
/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.security.plugins;
import java.io.File;
import java.net.URL;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.util.Hashtable;
import java.util.ArrayList;
import java.util.Iterator;
import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.Reference;
import javax.naming.RefAddr;
import javax.naming.StringRefAddr;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import javax.naming.spi.NamingManager;
import javax.naming.CommunicationException;
import javax.naming.CannotProceedException;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.security.auth.login.Configuration;
import org.jboss.logging.Log;
import org.jboss.util.ServiceMBeanSupport;
import org.jboss.security.EJBSecurityManager;
import org.jnp.server.NamingServer;
import org.jnp.interfaces.NamingContext;
/**
* This is a JMX service which manages JaasSecurityManagers.
* JaasSecurityManagers are responsible for validating credentials
* associated with principals.
*
* @see JaasSecurityManager
* @author <a href="[EMAIL PROTECTED]">Oleg Nitz</a>
* @author <a href="[EMAIL PROTECTED]">Rickard Oberg</a>
*/
public class JaasSecurityManagerService
extends ServiceMBeanSupport
implements JaasSecurityManagerServiceMBean, ObjectFactory {
MBeanServer server;
static NamingServer srv;
static Hashtable jsmMap = new Hashtable();
public String getName()
{
return "JAAS Security Manager";
}
protected ObjectName getObjectName(MBeanServer server, ObjectName name)
throws javax.management.MalformedObjectNameException
{
this.server = server;
return new ObjectName(OBJECT_NAME);
}
protected void startService() throws Exception
{
srv = new NamingServer();
InitialContext ic = new InitialContext();
// Bind reference to SM subcontext in JNDI
// Uses JNDI federation to handle the "java:jaas" context ourselves
RefAddr refAddr = new StringRefAddr("nns", "JSM");
Reference jsmsRef = new Reference("javax.naming.Context",
refAddr,getClass().getName(), null);
Context ctx = (Context)new InitialContext();
ctx.rebind("java:/jaas", jsmsRef);
}
protected void stopService()
{
InitialContext ic;
try
{
ic = new InitialContext();
ic.unbind("java:/jaas");
} catch (CommunicationException e)
{
// Do nothing, the naming services is already stopped
} catch (Exception e)
{
log.exception(e);
}
}
// ObjectFactory implementation ----------------------------------
/**
* Object factory implementation. This method is a bit tricky as it is called
twice for each
* JSM lookup. Let's say the lookup is for "java:jaas/MySecurity". Then this will
first be
* called as JNDI starts the "jaas" federation. In that call we make sure that
the next call
* will go through, i.e. we check that the "MySecurity" binding is availble. Then
we return
* the implementation of the "jaas" context. Then, when the "MySecurity" is
dereferenced we
* look up the JSM from an internal static hash table.
*
* Note: it is possible to break this by doing the lookup in two phases: first
lookup "java:jaas"
* and then do a second lookup of "MySecurity". If that is done then the first
lookup has no way of
* knowing what name to check (i.e. it can't make sure that "MySecurity" is
available in the
* "java:jaas" context!
*
* @param obj
* @param name
* @param nameCtx
* @param environment
* @return
* @exception Exception
*/
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable
environment)
throws Exception
{
if (name != null)
{
// Handle JaasSecurityManager lookup
if (name.size() == 0)
return nameCtx;
return jsmMap.get(name);
} else
{
// Handle "java:jaas" context
CannotProceedException cpe =
(CannotProceedException)environment.get(NamingManager.CPE);
Name remainingName = cpe.getRemainingName();
Context ctx = new NamingContext(environment, null, srv);
// Make sure that JSM is available
try
{
srv.lookup(remainingName);
} catch (Exception e)
{
// Not found - add reference to JNDI, and a real JSM to a map
Reference jsmRef = new Reference(JaasSecurityManager.class.getName(),
getClass().getName(), null);
ctx.rebind(remainingName, jsmRef);
jsmMap.put(remainingName, new
JaasSecurityManager(remainingName.toString()));
}
return ctx;
}
}
}
1.1
jboss/src/main/org/jboss/security/plugins/JaasSecurityManagerServiceMBean.java
Index: JaasSecurityManagerServiceMBean.java
===================================================================
/*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.security.plugins;
public interface JaasSecurityManagerServiceMBean
extends org.jboss.util.ServiceMBean
{
// Constants -----------------------------------------------------
public static final String OBJECT_NAME = ":service=JaasSecurityManager";
// Public --------------------------------------------------------
}
1.1 jboss/src/main/org/jboss/security/plugins/package.html
Index: package.html
===================================================================
Security plugins: implementations for use in the real life.