/* * JOSSO: Java Open Single Sign-On * * Copyright 2004-2009, Atricore, Inc. * * 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 should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.josso.gateway.identity.service.store.db; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.josso.auth.Credential; import org.josso.auth.CredentialKey; import org.josso.auth.CredentialProvider; import org.josso.auth.BaseCredential; import org.josso.auth.exceptions.SSOAuthenticationException; import org.josso.auth.scheme.AuthenticationScheme; import org.josso.gateway.SSONameValuePair; import org.josso.gateway.identity.exceptions.SSOIdentityException; import org.josso.gateway.identity.service.BaseRole; import org.josso.gateway.identity.service.BaseRoleImpl; import org.josso.gateway.identity.service.BaseUser; import org.josso.gateway.identity.service.BaseUserImpl; import org.josso.gateway.identity.service.store.CertificateUserKey; import org.josso.gateway.identity.service.store.SimpleUserKey; import org.w3c.dom.Element; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.security.cert.X509Certificate; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.Map.Entry; /** * JDBC Identity DAO, used by AbstractDBIdentityStore. * * @author <a href="mailto:sgonzalez@josso.org">Sebastian Gonzalez Oyuela</a> * @version $Id: IdentityDAO.java 543 2008-03-18 21:34:58Z sgonzalez $ */ public class IdentityDAO { private static final Log logger = LogFactory.getLog(IdentityDAO.class); private Connection _conn; private CredentialProvider _cp; private String _userQueryString; private String _rolesQueryString; private String _credentialsQueryString; private String _certificateCredentialsQueryString; private String _uidQueryString; private String _userPropertiesQueryString; private int _userPropertiesQueryVariables = 0; // This will be calcultated when setting _userPropertiesQueryString private String _resetCredentialDml; private String _relayCredentialQueryString; public IdentityDAO(Connection conn, CredentialProvider cp, String userQueryString, String rolesQueryString, String credentialsQueryString, String userPropertiesQueryString, String resetCredentialDml, String relayCredentialQueryString, String certificateCredentialsQueryString, String uidQueryString) { _conn = conn; _cp = cp; _userQueryString = userQueryString; _rolesQueryString = rolesQueryString; _credentialsQueryString = credentialsQueryString; _certificateCredentialsQueryString = certificateCredentialsQueryString; _uidQueryString = uidQueryString; _resetCredentialDml = resetCredentialDml; _relayCredentialQueryString = relayCredentialQueryString; // User properties query : if (userPropertiesQueryString != null) { _userPropertiesQueryString = userPropertiesQueryString; _userPropertiesQueryVariables = countQueryVariables(_userPropertiesQueryString); } } public BaseUser selectUser(SimpleUserKey key) throws SSOIdentityException { PreparedStatement stmt = null; ResultSet result = null; try { stmt = createPreparedStatement(_userQueryString); stmt.setString(1, key.getId()); result = stmt.executeQuery(); BaseUser user = fetchUser(result); if (user == null) throw new SSOIdentityException("Can't find user for : " + key); return user; } catch (SQLException sqlE) { logger.error("SQLException while listing user", sqlE); throw new SSOIdentityException("During user listing: " + sqlE.getMessage()); } catch (IOException ioE) { logger.error("IOException while listing user", ioE); throw new SSOIdentityException("During user listing: " + ioE.getMessage()); } catch (Exception e) { logger.error("Exception while listing user", e); throw new SSOIdentityException("During user listing: " + e.getMessage()); } finally { closeResultSet(result); closeStatement(stmt); } } public BaseRole[] selectRolesByUserKey(SimpleUserKey key) throws SSOIdentityException { PreparedStatement stmt = null; ResultSet result = null; try { stmt = createPreparedStatement(_rolesQueryString); stmt.setString(1, key.getId()); result = stmt.executeQuery(); BaseRole[] roles = fetchRoles(result); return roles; } catch (SQLException sqlE) { logger.error("SQLException while listing roles", sqlE); throw new SSOIdentityException("During roles listing: " + sqlE.getMessage()); } catch (IOException ioE) { logger.error("IOException while listing roles", ioE); throw new SSOIdentityException("During roles listing: " + ioE.getMessage()); } catch (Exception e) { logger.error("Exception while listing roles", e); throw new SSOIdentityException("During roles listing: " + e.getMessage()); } finally { closeResultSet(result); closeStatement(stmt); } } public Credential[] selectCredentials(SimpleUserKey key) throws SSOIdentityException { PreparedStatement stmt = null; ResultSet result = null; try { String credentialsQueryString = _credentialsQueryString; String schemeName = null; if (_cp instanceof AuthenticationScheme) { schemeName = ((AuthenticationScheme) _cp).getName(); } if ("strong-authentication".equals(schemeName)) { credentialsQueryString = _certificateCredentialsQueryString; } if (logger.isDebugEnabled()) logger.debug("[selectCredemtiasl()]]: key=" + key.getId()); stmt = createPreparedStatement(credentialsQueryString); stmt.setString(1, key.getId()); result = stmt.executeQuery(); Credential[] creds = fetchCredentials(result); return creds; } catch (SQLException sqlE) { logger.error("SQLException while listing credentials", sqlE); throw new SSOIdentityException("During credentials listing: " + sqlE.getMessage()); } catch (IOException ioE) { logger.error("IOException while listing credentials", ioE); throw new SSOIdentityException("During credentials listing: " + ioE.getMessage()); } catch (Exception e) { logger.error("Exception while listing credentials", e); throw new SSOIdentityException("During credentials listing: " + e.getMessage()); } finally { closeResultSet(result); closeStatement(stmt); } } public String loadUID(CredentialKey key) throws SSOIdentityException { PreparedStatement stmt = null; ResultSet result = null; String uid = null; try { if (key instanceof CertificateUserKey) { X509Certificate certificate = ((CertificateUserKey)key).getCertificate(); if (certificate != null) { stmt = createPreparedStatement(_uidQueryString); stmt.setBytes(1, certificate.getEncoded()); // Use key id to narrow search scope? //stmt.setString(2, ((CertificateUserKey)key).getId()); result = stmt.executeQuery(); while (result.next()) { uid = result.getString(1); break; } } } else if (key instanceof SimpleUserKey) { uid = ((SimpleUserKey)key).getId(); } } catch (SQLException sqlE) { logger.error("SQLException while finding UID", sqlE); throw new SSOIdentityException("During UID lookup: " + sqlE.getMessage()); } catch (Exception e) { logger.error("Exception while finding UID", e); throw new SSOIdentityException("During UID lookup: " + e.getMessage()); } finally { closeResultSet(result); closeStatement(stmt); } return uid; } /** * This will execute the configured query to get all user properties. * Because we sugested the use of 'UNION' key words to retrieve properties from multiple columns/tables, * we probably must send multiple times the username value to "avoid not all variables bound" error. * We could avoid this by using JDBC 3.0 drivers in the future. * * @param key * @throws SSOIdentityException */ public SSONameValuePair[] selectUserProperties(SimpleUserKey key) throws SSOIdentityException { PreparedStatement stmt = null; ResultSet result = null; try { if (logger.isDebugEnabled()) logger.debug("[selectUserProperties()]]: key=" + key.getId()); stmt = createPreparedStatement(_userPropertiesQueryString); // We don't jave JDBC 3.0 drivers, so ... bind all variables manually for (int i = 1; i <= _userPropertiesQueryVariables; i++) { stmt.setString(i, key.getId()); } result = stmt.executeQuery(); SSONameValuePair[] props = fetchSSONameValuePairs(result); return props; } catch (SQLException sqlE) { logger.error("SQLException while listing user properties", sqlE); throw new SSOIdentityException("During user properties listing: " + sqlE.getMessage()); } catch (IOException ioE) { logger.error("IOException while listing user properties", ioE); throw new SSOIdentityException("During user properties listing: " + ioE.getMessage()); } catch (Exception e) { logger.error("Exception while listing user properties", e); throw new SSOIdentityException("During user properties listing: " + e.getMessage()); } finally { closeResultSet(result); closeStatement(stmt); } } public void resetCredential ( SimpleUserKey key, BaseCredential newPassword) throws SSOIdentityException { PreparedStatement stmt = null; try { if (logger.isDebugEnabled()) logger.debug("[resetCredential()]]: key=" + key.getId()); stmt = createPreparedStatement( _resetCredentialDml ); stmt.setString( 1, newPassword.getValue().toString() ); stmt.setString( 2, key.getId() ); stmt.execute(); _conn.commit(); } catch (SQLException e) { logger.error("SQLException while updating user credential", e); throw new SSOIdentityException("During user update credential: " + e.getMessage()); } finally { closeStatement( stmt ); } } public String resolveUsernameByRelayCredential(String name, String value) throws SSOIdentityException { PreparedStatement stmt = null; ResultSet result = null; try { if (logger.isDebugEnabled()) logger.debug("[resolveUsernameByRelayCredential(name, value)]]: name=" + name + " value=" + value); if( _relayCredentialQueryString.contains( "#?#" )){ stmt = createPreparedStatement( _relayCredentialQueryString.replace( "#?#", name )); stmt.setString( 1, value ); } else { stmt = createPreparedStatement( _relayCredentialQueryString); stmt.setString( 1, name ); stmt.setString( 2, value ); } result = stmt.executeQuery(); String username = result.next() ? result.getString( 1 ) : null; if( result.next() ){ throw new SSOIdentityException( "Statement " + stmt + " returned more than one row" ); } return username; } catch (SQLException sqlE) { logger.error("SQLException while loading user with relay credential", sqlE); throw new SSOIdentityException("During load user with relay credential: " + sqlE.getMessage()); } catch (Exception e) { logger.error("Exception while loading user with relay credential", e); throw new SSOIdentityException("During load user with relay credential: " + e.getMessage()); } finally { closeResultSet(result); closeStatement(stmt); } } // ------------------------------------------------------------------------------------------ // Protected DB utils. // ------------------------------------------------------------------------------------------ /** * Builds an array of credentials based on a ResultSet * Column names are used to build a credential. */ protected Credential[] fetchCredentials(ResultSet rs) throws SQLException, IOException, SSOAuthenticationException { List creds = new ArrayList(); while (rs.next()) { ResultSetMetaData md = rs.getMetaData(); // Each column is a credential, the column name is used as credential name ... for (int i = 1; i <= md.getColumnCount(); i++) { String cName = md.getColumnLabel(i); Object credentialObject = rs.getObject(i); String credentialValue = null; // if the attribute value is an array, cast it to byte[] and then convert to // String using proper encoding if (credentialObject.getClass().isArray()) { try { // Try to create a UTF-8 String, we use java.nio to handle errors in a better way. // If the byte[] cannot be converted to UTF-8, we're using the credentialObject as is. byte[] credentialData = (byte[]) credentialObject; ByteBuffer in = ByteBuffer.allocate(credentialData.length); in.put(credentialData); in.flip(); Charset charset = Charset.forName("UTF-8"); CharsetDecoder decoder = charset.newDecoder(); CharBuffer charBuffer = decoder.decode(in); credentialValue = charBuffer.toString(); } catch (CharacterCodingException e) { if (logger.isDebugEnabled()) logger.debug("Can't convert credential value to String using UTF-8"); } } else if (credentialObject instanceof String) { // The credential value must be a String ... credentialValue = (String) credentialObject; } Credential c = null; if (credentialValue != null) { c = _cp.newCredential(cName, credentialValue); } else { c = _cp.newCredential(cName, credentialObject); } if (c != null) { creds.add(c); } } } return (Credential[]) creds.toArray(new Credential[creds.size()]); } /** * Builds an array of name-value pairs on a ResultSet * The resultset must have two columns, the first one contains names and the second one values. */ protected SSONameValuePair[] fetchSSONameValuePairs(ResultSet rs) throws SQLException, IOException, SSOAuthenticationException { List props = new ArrayList(); while (rs.next()) { // First column is a name and second is a value. String cName = rs.getString(1); String cValue = rs.getString(2); SSONameValuePair prop = new SSONameValuePair(cName, cValue); props.add(prop); } return (SSONameValuePair[]) props.toArray(new SSONameValuePair[props.size()]); } /** * Builds a user based on a result set. * ResultSet must have one and only one record. */ protected BaseRole[] fetchRoles(ResultSet rs) throws SQLException, IOException { List roles = new ArrayList(); while (rs.next()) { BaseRole role = new BaseRoleImpl(); String rolename = rs.getString(1); role.setName(rolename); roles.add(role); } return (BaseRole[]) roles.toArray(new BaseRole[roles.size()]); } /** * Builds a user based on a result set. */ protected BaseUser fetchUser(ResultSet rs) throws SQLException, IOException { if (rs.next()) { BaseUser user = new BaseUserImpl(); String username = rs.getString(1); user.setName(username); return user; } return null; } /** * Creates a new prepared statement for the received query string. * * @param query * @throws SQLException */ private PreparedStatement createPreparedStatement(String query) throws SQLException { if (logger.isDebugEnabled()) logger.debug("[createPreparedStatement()] : " + "(" + query + ")"); PreparedStatement stmt = _conn.prepareStatement(query + " "); return stmt; } protected void closeStatement(PreparedStatement stmt) throws SSOIdentityException { try { if (stmt != null) { stmt.close(); } } catch (SQLException se) { if (logger.isDebugEnabled()) { logger.debug("Error clossing statement"); } throw new SSOIdentityException("Error while clossing statement: \n " + se.getMessage()); } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("Error clossing statement"); } // throw new SSOIdentityException("Error while clossing statement: \n " + e.getMessage()); } } protected void closeResultSet(ResultSet result) throws SSOIdentityException { try { if (result != null) { result.close(); } } catch (SQLException se) { if (logger.isDebugEnabled()) { logger.debug("Error while clossing result set"); } throw new SSOIdentityException("SQL Exception while closing\n" + se.getMessage()); } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("Error while clossing result set"); } // throw new SSOIdentityException("Exception while closing Result Set\n" + e.getMessage()); } } /** * This util counts the number of times that the '?' char appears in the received query string. */ protected int countQueryVariables(String qry) { StringTokenizer st = new StringTokenizer(qry, "?", true); int count = 0; while (st.hasMoreTokens()) { String tk = st.nextToken(); if ("?".equals(tk)) { count++; } } return count; } }