package net.java.glassfish.security.auth.realm.jdbc; import com.sun.enterprise.security.LoginException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.logging.Logger; import java.util.logging.Level; import com.sun.logging.LogDomains; import com.sun.gjc.spi.DataSource; import com.sun.enterprise.security.acl.RoleMapper; import com.sun.enterprise.security.auth.realm.IASRealm; import com.sun.enterprise.security.auth.realm.BadRealmException; import com.sun.enterprise.security.auth.realm.NoSuchUserException; import com.sun.enterprise.security.auth.realm.NoSuchRealmException; import com.sun.enterprise.security.auth.realm.AuthenticationHandler; import com.sun.enterprise.security.auth.realm.InvalidOperationException; import javax.naming.InitialContext; //import javax.sql.DataSource; /** * Realm for supporting JDBC authentication. * *

The JDBC realm needs the following properties in its configuration: *

* * @see com.sun.enterprise.security.auth.login.SolarisLoginModule * */ public final class JDBCRealm extends IASRealm { // Descriptive string of the authentication type of this realm. public static final String AUTH_TYPE = "jdbc"; public static final String KEY_FIND_IDENTIFIER ="find-user-query"; public static final String KEY_FIND_GROUPS = "find-groups-query"; public static final String KEY_DATASOURCE = "datasource"; public static final String KEY_USER = "user"; public static final String KEY_PASSWORD = "password"; private HashMap groupCache; private Vector emptyVector; /** * Initialize a realm with some properties. This can be used * when instantiating realms from their descriptions. This * method may only be called a single time. * * @param props Initialization parameters used by this realm. * @exception BadRealmException If the configuration parameters * identify a corrupt realm. * @exception NoSuchRealmException If the configuration parameters * specify a realm which doesn't exist. * @todo replace I18N code with new one specific to JDBC */ public synchronized void init(Properties props) throws BadRealmException, NoSuchRealmException{ String jaasCtx = props.getProperty(IASRealm.JAAS_CONTEXT_PARAM); String findIdentifierQuery = props.getProperty(KEY_FIND_IDENTIFIER); String findGroupsQuery = props.getProperty(KEY_FIND_GROUPS); String user = props.getProperty(KEY_USER); String password = props.getProperty(KEY_PASSWORD); String datasource = props.getProperty(KEY_DATASOURCE); if (jaasCtx==null) { _logger.warning("realmconfig.noctx"); String msg = sm.getString("solarisrealm.nojaas"); throw new BadRealmException(msg); } if (findIdentifierQuery==null) { _logger.warning("realmconfig.noidentifierquery"); //String msg = sm.getString("solarisrealm.nojaas"); //throw new BadRealmException(msg); throw new BadRealmException("No property "+KEY_FIND_IDENTIFIER+" set"); } if (findIdentifierQuery==null) { _logger.warning("realmconfig.nogroupsquery"); //String msg = sm.getString("solarisrealm.nojaas"); //throw new BadRealmException(msg); throw new BadRealmException("No property "+KEY_FIND_GROUPS+" set"); } if (user==null) { _logger.warning("realmconfig.nouser"); //String msg = sm.getString("solarisrealm.nojaas"); //throw new BadRealmException(msg); throw new BadRealmException("No property "+KEY_USER+" set"); } if (password==null) { _logger.warning("realmconfig.nopassword"); //String msg = sm.getString("solarisrealm.nojaas"); //throw new BadRealmException(msg); throw new BadRealmException("No property "+KEY_PASSWORD+" set"); } if (datasource==null) { _logger.warning("realmconfig.nodatasource"); //String msg = sm.getString("solarisrealm.nojaas"); //throw new BadRealmException(msg); throw new BadRealmException("No property "+KEY_DATASOURCE+" set"); } this.setProperty(IASRealm.JAAS_CONTEXT_PARAM, jaasCtx); this.setProperty(KEY_FIND_IDENTIFIER,findIdentifierQuery); this.setProperty(KEY_FIND_GROUPS,findGroupsQuery); this.setProperty(KEY_USER,user); this.setProperty(KEY_PASSWORD,password); this.setProperty(KEY_DATASOURCE,datasource); _logger.fine("JDBCRealm : "+IASRealm.JAAS_CONTEXT_PARAM+ "="+jaasCtx+" "+KEY_FIND_GROUPS+"="+findIdentifierQuery+" "+KEY_FIND_GROUPS+"="+findGroupsQuery); groupCache = new HashMap(); emptyVector = new Vector(); } /** * Returns a short (preferably less than fifteen characters) description * of the kind of authentication which is supported by this realm. * * @return Description of the kind of authentication that is directly * supported by this realm. */ public String getAuthType(){ return AUTH_TYPE; } /** * Returns the name of all the groups that this user belongs to. * This is called from web path role verification, though * it should not be. * * @param username Name of the user in this realm whose group listing * is needed. * @return Enumeration of group names (strings). * @exception InvalidOperationException thrown if the realm does not * support this operation - e.g. Certificate realm does not support * this operation. */ public Enumeration getGroupNames (String username) throws InvalidOperationException, NoSuchUserException { Vector v = (Vector)groupCache.get(username); if (v == null) { v = loadGroupNames(username); } return v.elements(); } /** * Set group membership info for a user. * *

See bugs 4646133,4646270 on why this is here. * */ public void setGroupNames(String username, String[] groups) { Vector v = null; if (groups == null) { v = emptyVector; } else { v = new Vector(groups.length + 1); for (int i=0; iGroup info is loaded when user authenticates, however in some * cases (such as run-as) the group membership info is needed * without an authentication event. * */ private Vector loadGroupNames(String username){ String[] grps = findGroups(username); if (grps == null) { _logger.fine("No groups returned for user: "+username); } setGroupNames(username, grps); return (Vector)groupCache.get(username); } /** * Test if a user is valid * @param user user's identifier * @param password user's password * @return true if valid */ private boolean isUserValid(String user, String password){ Connection connection=null; try{ connection = getConnection(); final String identifierQuery = this.getProperty(KEY_FIND_IDENTIFIER); final PreparedStatement statement = connection.prepareStatement(identifierQuery); statement.setString(1,user); statement.setString(2,password); final ResultSet result = statement.executeQuery(); final boolean valid = result.next(); _logger.info("User validity:"+valid); return valid; }catch(Exception lex){ // Provide isolation, if any exception has happen we can not access ! _logger.log(Level.SEVERE,"Problem while testing user:"+user,lex); return false; }finally{ if(connection!=null){ try{ connection.close(); }catch(SQLException s){ _logger.log(Level.WARNING,"Was not able to close connection",s); } } } } /** * Delegate method for retreiving users groups * @param user user's identifier * @return array of group key */ private String[] findGroups(String user){ Connection connection=null; try{ connection = getConnection(); final String identifierQuery = this.getProperty(KEY_FIND_GROUPS); final PreparedStatement statement = connection.prepareStatement(identifierQuery); statement.setString(1,user); final ResultSet result = statement.executeQuery(); final List groups = new ArrayList(); while(result.next()){ groups.add(result.getString(1)); } final String[] groupArray = new String[groups.size()]; _logger.info("JDBC Groups:"+groups+" for user:"+user); return groups.toArray(groupArray); }catch(Exception lex){ // Provide isolation, if any exception has happen we can not access ! _logger.log(Level.SEVERE,"Problem while testing user:"+user,lex); return null; }finally{ // Do some early cleaning if(connection!=null){ try{ connection.close(); }catch(SQLException s){ _logger.log(Level.WARNING,"Was not able to close connection",s); } } } } /** * Return a connection from the properties configured * @return a connection * @todo bring some cache to speedup AA */ private Connection getConnection() throws LoginException { final String binding = this.getProperty(KEY_DATASOURCE); final String userName = this.getProperty(KEY_USER); final String password = this.getProperty(KEY_PASSWORD); try{ final InitialContext ic = new InitialContext(); final DataSource dataSource = (DataSource) ic.lookup(binding); final Connection connection = dataSource.getNonTxConnection(userName,password); connection.setAutoCommit(false); // Usefull ? return connection; }catch(Exception ex){ // Whatever has happend this is a show stopper, so wrap it and throw it LoginException loginEx = new LoginException("Unable to connect to datasource "+ binding+" using login "+userName+" and password "+password); loginEx.initCause(ex); throw loginEx; } } }