You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by rh...@apache.org on 2012/01/19 14:51:14 UTC

svn commit: r1233377 - in /db/derby/code/trunk/java: engine/org/apache/derby/ engine/org/apache/derby/catalog/ engine/org/apache/derby/iapi/jdbc/ engine/org/apache/derby/iapi/reference/ engine/org/apache/derby/iapi/services/monitor/ engine/org/apache/d...

Author: rhillegas
Date: Thu Jan 19 13:51:13 2012
New Revision: 1233377

URL: http://svn.apache.org/viewvc?rev=1233377&view=rev
Log:
First version of NATIVE authentication service, including first tranche of tests for it.

Added:
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/NativeAuthenticationServiceImpl.java   (with props)
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthenticationServiceTest.java   (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/AuthenticationService.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/monitor/ModuleFactory.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/SpecificAuthenticationServiceImpl.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/BaseMonitor.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
    db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml
    db/derby/code/trunk/java/engine/org/apache/derby/modules.properties
    db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
    db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DatabaseChangeSetup.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DriverManagerConnector.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DropDatabaseSetup.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SystemPropertyTestSetup.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/catalog/SystemProcedures.java Thu Jan 19 13:51:13 2012
@@ -2051,10 +2051,26 @@ public class SystemProcedures  {
          )
         throws SQLException
     {
+        LanguageConnectionContext lcc = ConnectionUtil.getCurrentLCC();
+        TransactionController tc = lcc.getTransactionExecute();
+
+        addUser( userName, password, tc );
+    }
+    /**
+     * Create a new user (this entry is called when bootstrapping the credentials of the DBO
+     * at database creation time.
+     */
+    public static void addUser
+        (
+         String userName,
+         String password,
+         TransactionController tc
+         )
+        throws SQLException
+    {
         try {
             LanguageConnectionContext lcc = ConnectionUtil.getCurrentLCC();
             DataDictionary dd = lcc.getDataDictionary();
-            TransactionController tc = lcc.getTransactionExecute();
 
             /*
             ** Inform the data dictionary that we are about to write to it.
@@ -2067,7 +2083,7 @@ public class SystemProcedures  {
             */
             dd.startWriting(lcc);
 
-            UserDescriptor  userDescriptor = makeUserDescriptor( lcc, userName, password );
+            UserDescriptor  userDescriptor = makeUserDescriptor( dd, tc, userName, password );
 
             dd.addDescriptor( userDescriptor, null, DataDictionary.SYSUSERS_CATALOG_NUM, false, tc );
             
@@ -2075,15 +2091,15 @@ public class SystemProcedures  {
     }
     private static  UserDescriptor  makeUserDescriptor
         (
-         LanguageConnectionContext lcc,
+         DataDictionary dd,
+         TransactionController tc,
          String userName,
          String password
          )
         throws StandardException
     {
-        DataDictionary dd = lcc.getDataDictionary();
         DataDescriptorGenerator ddg = dd.getDataDescriptorGenerator();
-        PasswordHasher hasher = dd.makePasswordHasher( lcc.getTransactionExecute().getProperties() );
+        PasswordHasher hasher = dd.makePasswordHasher( tc.getProperties() );
 
         if ( hasher == null )
         {
@@ -2127,7 +2143,7 @@ public class SystemProcedures  {
             */
             dd.startWriting(lcc);
 
-            UserDescriptor  userDescriptor = makeUserDescriptor( lcc, userName, password );
+            UserDescriptor  userDescriptor = makeUserDescriptor( dd, tc, userName, password );
 
             dd.updateUser( userDescriptor, tc );
             

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/AuthenticationService.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/AuthenticationService.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/AuthenticationService.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/jdbc/AuthenticationService.java Thu Jan 19 13:51:13 2012
@@ -40,11 +40,20 @@ public interface AuthenticationService 
 	public static final String MODULE =
 								"org.apache.derby.iapi.jdbc.AuthenticationService";
 	/**
-	 * Authenticate a User inside JBMS.
+	 * Authenticate a User inside Derby.
 	 *
 	 * @param info			Connection properties info.
 	 * failure.
 	 */
 	public boolean authenticate(String databaseName, Properties info)
 	  throws SQLException;
+
+    /**
+     * <p>
+     * Get the name of the credentials database used to authenticate system-wide operations.
+     * This returns null for all implementations except NATIVE authentication.
+     * </p>
+     */
+    public  String  getSystemCredentialsDatabaseName();
+
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Property.java Thu Jan 19 13:51:13 2012
@@ -797,6 +797,9 @@ public interface Property { 
 
 	// These are the different built-in providers Derby supports
 
+	public static final String AUTHENTICATION_PROVIDER_NATIVE =
+								"NATIVE:";
+
 	public static final String AUTHENTICATION_PROVIDER_BUILTIN =
 								"BUILTIN";
 
@@ -806,6 +809,15 @@ public interface Property { 
 	public static final String AUTHENTICATION_SERVER_PARAMETER =
 								"derby.authentication.server";
 
+    // this suffix on the NATIVE authentication provider means that
+    // database operations should be authenticated locally
+	public static final String AUTHENTICATION_PROVIDER_LOCAL_SUFFIX =
+								":LOCAL";
+
+    // when local native authentication is enabled, we store this value for derby.authentication.provider
+    public static final String AUTHENTICATION_PROVIDER_NATIVE_LOCAL =
+        AUTHENTICATION_PROVIDER_NATIVE + AUTHENTICATION_PROVIDER_LOCAL_SUFFIX;
+    
     /**
      * Property that specifies the name of the hash algorithm to use with
      * the configurable hash authentication scheme.

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/monitor/ModuleFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/monitor/ModuleFactory.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/monitor/ModuleFactory.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/monitor/ModuleFactory.java Thu Jan 19 13:51:13 2012
@@ -227,6 +227,13 @@ public interface ModuleFactory
 
 
 	/**
+		Canonicalize a service name, mapping different user-specifications of a database name
+        onto a single, standard name.
+	*/
+    public  String  getCanonicalServiceName( String userSpecifiedName )
+        throws StandardException;
+    
+	/**
 		Find a service.
 
 		<BR>

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java Thu Jan 19 13:51:13 2012
@@ -562,6 +562,39 @@ public class PropertyUtil {
 		return false;
 	}
 
+	/**
+		Return true if NATIVE authentication has been enabled in the passed-in properties.
+	*/
+	public static boolean nativeAuthenticationEnabled( Properties properties )
+    {
+		String authenticationProvider = getPropertyFromSet
+            (
+             properties,
+             Property.AUTHENTICATION_PROVIDER_PARAMETER
+             );
+
+        if ( authenticationProvider ==  null ) { return false; }
+
+        return StringUtil.SQLToUpperCase( authenticationProvider ).startsWith( Property.AUTHENTICATION_PROVIDER_NATIVE );
+	}
+
+	/**
+		Return true if the passed-in properties specify NATIVE authentication using LOCAL credentials.
+	*/
+	public static boolean localNativeAuthenticationEnabled( Properties properties )
+    {
+        if ( ! nativeAuthenticationEnabled( properties ) ) { return false; }
+        
+		String authenticationProvider = getPropertyFromSet
+            (
+             properties,
+             Property.AUTHENTICATION_PROVIDER_PARAMETER
+             );
+
+        return StringUtil.SQLToUpperCase( authenticationProvider ).endsWith
+            ( Property.AUTHENTICATION_PROVIDER_LOCAL_SUFFIX );
+	}
+
 
 	/**
 	 * Return true if username is defined as a system property

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java Thu Jan 19 13:51:13 2012
@@ -1692,6 +1692,17 @@ public interface DataDictionary
 	public void updateUser( UserDescriptor newDescriptor,TransactionController tc )
 		throws StandardException;
 
+	/**
+	 * Return the credentials descriptor for the named user.
+	 *
+	 * @param userName      Name of the user whose credentials we want.
+	 * @param tc					The TransactionController to use
+	 *
+	 * @exception StandardException		Thrown on failure
+	 */
+	public UserDescriptor getUser( String userName, TransactionController tc )
+		throws StandardException;
+
 	/** 
 	 * Drop a User from the DataDictionary
 	 *

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java Thu Jan 19 13:51:13 2012
@@ -397,9 +397,11 @@ public abstract class EmbedConnection im
 					addWarning(SQLWarningFactory.newSQLWarning(SQLState.DATABASE_EXISTS, getDBName()));
 				} else {
 
-					// check for user's credential and authenticate the user
-					// with system level authentication service.
-					checkUserCredentials(null, info);
+                    //
+					// Check for user's credential and authenticate the user
+					// with the system level authentication service.
+                    //
+                    checkUserCredentials( true, null, info );
 					
 					// Process with database creation
 					database = createDatabase(tr.getDBName(), info);
@@ -417,7 +419,7 @@ public abstract class EmbedConnection im
 			// the database
 			//
             try {
-                checkUserCredentials(tr.getDBName(), info);
+                checkUserCredentials( false, tr.getDBName(), info );
             } catch (SQLException sqle) {
                 if (isStartSlaveBoot && !slaveDBAlreadyBooted) {
                     // Failing credentials check on a previously
@@ -1170,7 +1172,7 @@ public abstract class EmbedConnection im
 	//
 	// Check passed-in user's credentials.
 	//
-	private void checkUserCredentials(String dbname,
+	private void checkUserCredentials( boolean creatingDatabase, String dbname,
 									  Properties userInfo)
 	  throws SQLException
 	{
@@ -1209,7 +1211,31 @@ public abstract class EmbedConnection im
 			throw newSQLException(SQLState.LOGIN_FAILED, failedString);
 		}
 
-		if (dbname != null) {
+        //
+        // We must handle the special case when the system uses NATIVE
+        // authentication for system-wide operations but we are being
+        // asked to create the system-wide credentials database. In this situation,
+        // the database holding the credentials does not exist yet. In this situation,
+        // we are supposed to create the credentials database and store the
+        // creation credentials in that database as the credentials of the system administrator.
+        //
+        if (
+            creatingDatabase &&
+            compareDatabaseNames( getDBName(), authenticationService.getSystemCredentialsDatabaseName() )
+            )
+        {
+            //
+            // NATIVE authentication using a system-wide credentials database
+            // which is being created now. Allow this to succeed.
+            //
+            return;
+        }
+
+        //
+        // If we are creating a database, we set the dbname
+        //
+
+        if (dbname != null) {
 			checkUserIsNotARole();
 		}
 
@@ -1232,6 +1258,23 @@ public abstract class EmbedConnection im
 			usingNoneAuth = true;
 	}
 
+    /**
+     * Compare two user-specified database names to see if they identify
+     * the same database.
+     */
+    private boolean compareDatabaseNames( String leftDBName, String rightDBName )
+        throws SQLException
+    {
+        try {
+            String  leftCanonical = Monitor.getMonitor().getCanonicalServiceName( leftDBName );
+            String  rightCanonical = Monitor.getMonitor().getCanonicalServiceName( rightDBName );
+
+            if ( leftCanonical == null ) { return false; }
+            else { return leftCanonical.equals( rightCanonical ); }
+            
+        } catch (StandardException se) { throw Util.generateCsSQLException(se); }
+    }
+
 
 	/**
 	 * If applicable, check that we don't connect with a user name

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/AuthenticationServiceBase.java Thu Jan 19 13:51:13 2012
@@ -260,6 +260,8 @@ public abstract class AuthenticationServ
 						 );
 	}
 
+    public  String  getSystemCredentialsDatabaseName()    { return null; }
+
 	/**
 	 * Returns a property if it was set at the database or
 	 * system level. Treated as SERVICE property by default.
@@ -273,11 +275,7 @@ public abstract class AuthenticationServ
 
 		try {
 
-		  if (store != null)
-          {
-            tc = store.getTransaction(
-                ContextService.getFactory().getCurrentContextManager());
-          }
+          tc = getTransaction();
 
 		  propertyValue =
 			PropertyUtil.getServiceProperty(tc,
@@ -295,6 +293,32 @@ public abstract class AuthenticationServ
 		return propertyValue;
 	}
 
+    /**
+     * <p>
+     * Get a transaction for performing authentication at the database level.
+     * </p>
+     */
+    protected   TransactionController   getTransaction()
+        throws StandardException
+    {
+        if ( store == null ) { return null; }
+        else
+        {
+            return store.getTransaction( ContextService.getFactory().getCurrentContextManager() );
+        }
+    }
+
+    /**
+     * <p>
+     * Get the name of the database if we are performing authentication at the database level.
+     * </p>
+     */
+    protected   String  getServiceName()
+    {
+        if ( store == null ) { return null; }
+        else { return Monitor.getServiceName( store ); }
+    }
+
 	public String getDatabaseProperty(String key) {
 
 		String propertyValue = null;
@@ -406,7 +430,12 @@ public abstract class AuthenticationServ
 					properties,
 					org.apache.derby.iapi.reference.Property.REQUIRE_AUTHENTICATION_PARAMETER
 														);
-		return Boolean.valueOf(requireAuthentication).booleanValue();
+		if ( Boolean.valueOf(requireAuthentication).booleanValue() ) { return true; }
+
+        //
+        // NATIVE authentication does not require that you set REQUIRE_AUTHENTICATION_PARAMETER.
+        //
+        return PropertyUtil.nativeAuthenticationEnabled( properties );
 	}
 
 	/**

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/NativeAuthenticationServiceImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/NativeAuthenticationServiceImpl.java?rev=1233377&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/NativeAuthenticationServiceImpl.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/NativeAuthenticationServiceImpl.java Thu Jan 19 13:51:13 2012
@@ -0,0 +1,473 @@
+/*
+
+   Derby - Class org.apache.derby.impl.jdbc.authentication.NativeAuthenticationServiceImpl
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derby.impl.jdbc.authentication;
+
+import java.util.Properties;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Arrays;
+import javax.sql.DataSource;
+
+import org.apache.derby.catalog.SystemProcedures;
+import org.apache.derby.iapi.db.Database;
+import org.apache.derby.iapi.reference.Property;
+import org.apache.derby.iapi.services.info.JVMInfo;
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;
+import org.apache.derby.iapi.sql.dictionary.PasswordHasher;
+import org.apache.derby.iapi.sql.dictionary.UserDescriptor;
+import org.apache.derby.iapi.reference.Attribute;
+import org.apache.derby.iapi.reference.SQLState;
+import org.apache.derby.authentication.UserAuthenticator;
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.services.monitor.Monitor;
+import org.apache.derby.iapi.services.property.PropertyUtil;
+import org.apache.derby.iapi.services.sanity.SanityManager;
+import org.apache.derby.iapi.store.access.TransactionController;
+import org.apache.derby.iapi.util.IdUtil;
+import org.apache.derby.iapi.util.StringUtil;
+import org.apache.derby.impl.jdbc.Util;
+
+/**
+ * <p>
+ * This authentication service supports Derby NATIVE authentication.
+ * </p>
+ *
+ * <p>
+ * To activate this service, set the derby.authentication.provider database
+ * or system property to a value beginning with the token "NATIVE:".
+ * </p>
+ *
+ * <p>
+ * This service instantiates and calls the basic User authentication scheme at runtime.
+ * </p>
+ *
+ * <p>
+ * User credentials are defined in the SYSUSERS table.
+ * </p>
+ *
+ */
+public final class NativeAuthenticationServiceImpl
+	extends AuthenticationServiceBase implements UserAuthenticator
+{
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTANTS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // STATE
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    // temporary, used when bootstrapping a locally authenticated database
+    private boolean _creatingCredentialsDB = false;
+    
+    private String      _credentialsDB;
+    private boolean _authenticateDatabaseOperationsLocally;
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // ModuleControl BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 *  Check if we should activate this authentication service.
+	 */
+	public boolean canSupport(Properties properties)
+    {
+		if (!requireAuthentication(properties)) { return false; }
+
+        if ( PropertyUtil.nativeAuthenticationEnabled( properties ) )
+        {
+            parseNativeSpecification( properties );
+
+            return true;
+        }
+        else { return false; }
+	}
+
+    /**
+     * <p>
+     * Parse the specification of NATIVE authentication. It can take 3 forms:
+     * </p>
+     *
+     * <ul>
+     * <li><i>NATIVE:$credentialsDB</i> - Here $credentialsDB is the name of a Derby database.
+     *  This means that all authentication should take place in $credentialsDB.</li>
+     * <li><i>NATIVE:$credentialsDB:LOCAL</i>- This means that system-wide operations (like engine shutdown)
+     *  are authenticated in $credentialsDB but connections to existing databases are authenticated
+     *  in those databases.</li>
+     * <li><i>NATIVE::LOCAL</i> - This means that connections to a given database are authenticated
+     *  in that database.</li>
+     * </ul>
+     */
+    private void    parseNativeSpecification( Properties properties )
+    {
+        // If we get here, we already know that the authentication provider property
+        // begins with the NATIVE: token
+        String authenticationProvider = PropertyUtil.getPropertyFromSet
+            (
+             properties,
+             Property.AUTHENTICATION_PROVIDER_PARAMETER
+             );
+
+        _authenticateDatabaseOperationsLocally = PropertyUtil.localNativeAuthenticationEnabled( properties );
+
+        // Everything between the first colon and the last colon is the name of a database
+        int     dbNameStartIdx = authenticationProvider.indexOf( ":" ) + 1;
+        int     dbNameEndIdx = _authenticateDatabaseOperationsLocally ?
+            authenticationProvider.lastIndexOf( ":" )
+            : authenticationProvider.length();
+
+        if ( dbNameEndIdx > dbNameStartIdx )
+        {
+            _credentialsDB = authenticationProvider.substring( dbNameStartIdx, dbNameEndIdx );
+
+            if ( _credentialsDB.length() == 0 ) { _credentialsDB = null; }
+        }
+    }
+
+    /**
+     * <p>
+     * Return true if AUTHENTICATION_PROVIDER_PARAMETER was well formatted.
+     * The property must have designated some database as the authentication authority.
+     * </p>
+     */
+    private boolean validAuthenticationProvider()
+    {
+        return (_credentialsDB != null) || _authenticateDatabaseOperationsLocally;
+    }
+
+	/**
+	 * @see org.apache.derby.iapi.services.monitor.ModuleControl#boot
+	 * @exception StandardException upon failure to load/boot the expected
+	 * authentication service.
+	 */
+	public void boot(boolean create, Properties properties)
+	  throws StandardException
+    {
+		// first perform the initialization in our superclass
+		super.boot( create, properties );
+
+        if ( !validAuthenticationProvider() )
+        {
+            throw StandardException.newException( SQLState.BAD_NATIVE_AUTH_SPEC );
+        }
+
+		// Initialize the MessageDigest class engine here
+		// (we don't need to do that ideally, but there is some
+		// overhead the first time it is instantiated.
+		try {
+			MessageDigest digestAlgorithm = MessageDigest.getInstance("SHA-1");
+			digestAlgorithm.reset();
+
+		} catch (NoSuchAlgorithmException nsae) {
+			throw Monitor.exceptionStartingModule(nsae);
+		}
+
+        // bootstrap the creation of the initial username/password when the dbo creates a credentials db
+        if ( create && authenticatingInThisService( getCanonicalServiceName() ) ) { _creatingCredentialsDB = true; }
+        else { _creatingCredentialsDB = false; }
+
+		// Set ourselves as being ready, having loaded the proper
+		// authentication scheme for this service
+		//
+		this.setAuthenticationService(this);
+	}
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // UserAuthenticator BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /** Override behavior in superclass */
+    public  String  getSystemCredentialsDatabaseName()    { return _credentialsDB; }
+    /** Override behavior in superclass */
+
+	/**
+	 * Authenticate the passed-in user's credentials.
+	 *
+	 * @param userName		The user's name used to connect to JBMS system
+	 * @param userPassword	The user's password used to connect to JBMS system
+	 * @param databaseName	The database which the user wants to connect to.
+	 * @param info			Additional jdbc connection info.
+	 */
+	public boolean	authenticateUser
+        (
+         String userName,
+         String userPassword,
+         String databaseName,
+         Properties info
+         )
+        throws SQLException
+	{
+        try {
+            // No "guest" user
+            if ( userName == null ) { return false; }
+
+            //
+            // We must handle these cases:
+            //
+            // 1) Database name is null. This means that we are authenticating a system-wide
+            // operation. The authentication must be done by the system-wide credentials database.
+            //
+            // 2) Database name is not null and authentication is NOT specified as local.
+            // This means that we are authenticating a database-specific operation
+            // in the system-wide credentials database. There are two subcases:
+            //
+            // 2a) The current database is NOT the credentials database. This reduces to case (1) above:
+            // authentication must be performed in another database.
+            //
+            // 2b) The current database IS the credentials database. This reduces to case (3) below:
+            // authentication must be performed in this database.
+            //
+            // 3) Database name is not null and authentication IS being performed locally in this database.
+            // This means that we are authenticating a database-specific operation and performing the
+            // authentication in this database.
+            //
+
+            if ( (databaseName == null) || !authenticatingInThisDatabase( databaseName ) )
+            {
+                return authenticateRemotely(  userName, userPassword, databaseName );
+            }
+            else
+            {
+                return authenticateLocally( userName, userPassword, databaseName );
+            }
+        }
+        catch (StandardException se)
+        {
+            throw Util.generateCsSQLException(se);
+        }
+	}
+
+    /**
+     * <p>
+     * Return true if we are authenticating in this database.
+     * </p>
+     */
+    private boolean authenticatingInThisDatabase( String userVisibleDatabaseName )
+        throws StandardException
+    {
+        return authenticatingInThisService( Monitor.getMonitor().getCanonicalServiceName( userVisibleDatabaseName ) );
+    }
+
+    /**
+     * <p>
+     * Return true if we are authenticating in this service.
+     * </p>
+     */
+    private boolean authenticatingInThisService( String canonicalDatabaseName )
+        throws StandardException
+    {
+        if ( _authenticateDatabaseOperationsLocally ) { return true; }
+        else { return isCredentialsService( canonicalDatabaseName ); }
+    }
+
+    /**
+     * <p>
+     * Return true if the passed in service is the credentials database.
+     * </p>
+     */
+    private boolean isCredentialsService( String canonicalDatabaseName )
+        throws StandardException
+    {
+        String  canonicalCredentialsDBName = getCanonicalServiceName( _credentialsDB );
+
+        String canonicalDB = Monitor.getMonitor().getCanonicalServiceName( canonicalDatabaseName );
+
+        if ( canonicalCredentialsDBName == null ) { return false; }
+        else { return canonicalCredentialsDBName.equals( canonicalDatabaseName ); }
+    }
+
+    /** Get the canonical name of the current database service */
+    private String  getCanonicalServiceName()
+        throws StandardException
+    {
+        return getCanonicalServiceName( getServiceName() );
+    }
+
+    /** Turn a service name into its normalized, standard form */
+    private String  getCanonicalServiceName( String rawName )
+        throws StandardException
+    {
+        return Monitor.getMonitor().getCanonicalServiceName( rawName );
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // AUTHENTICATE REMOTELY
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * Authenticate the passed-in credentials against another Derby database. This is done
+     * by getting a connection to the credentials database using the supplied username
+     * and password. If the connection attempts succeeds, then authentication succeeds.
+	 *
+	 * @param userName		The user's name used to connect to JBMS system
+	 * @param userPassword	The user's password used to connect to JBMS system
+	 * @param databaseName	The database which the user wants to connect to.
+	 */
+	private boolean	authenticateRemotely
+        (
+         String userName,
+         String userPassword,
+         String databaseName
+         )
+        throws StandardException
+	{
+        // this catches the case when someone specifies derby.authentication.provider=NATIVE::LOCAL
+        // at the system level
+        if ( _credentialsDB == null )
+        {
+            throw StandardException.newException( SQLState.BAD_NATIVE_AUTH_SPEC );
+        }
+        
+        String      dataSourceName = JVMInfo.J2ME ?
+            "org.apache.derby.jdbc.EmbeddedSimpleDataSource" :
+            "org.apache.derby.jdbc.EmbeddedDataSource";
+
+        try {
+            DataSource  dataSource = (DataSource) Class.forName( dataSourceName ).newInstance();
+
+            callDataSourceSetter( dataSource, "setDatabaseName", _credentialsDB );
+            callDataSourceSetter( dataSource, "setUser", userName );
+            callDataSourceSetter( dataSource, "setPassword", userPassword );
+
+            Connection  conn = dataSource.getConnection();
+            conn.close();
+        }
+        catch (ClassNotFoundException cnfe) { throw wrap( cnfe ); }
+        catch (InstantiationException ie) { throw wrap( ie ); }
+        catch (IllegalAccessException ie) { throw wrap( ie ); }
+        catch (SQLException se)
+        {
+            String  sqlState = se.getSQLState();
+
+            if ( SQLState.LOGIN_FAILED.equals( sqlState ) ) { return false; }
+            else if ( SQLState.DATABASE_NOT_FOUND.startsWith( sqlState ) )
+            {
+                throw StandardException.newException( SQLState.MISSING_CREDENTIALS_DB, _credentialsDB );
+            }
+            else { throw wrap( se ); }
+        }
+
+        // If we get here, then we successfully connected to the credentials database. Hooray.
+        return true;
+    }
+    /** Call a setter method on a DataSource via reflection */
+    private void callDataSourceSetter( DataSource ds, String methodName, String value )
+        throws StandardException
+    {
+        try {
+            ds.getClass().getMethod( methodName, new Class[] { String.class } ).invoke( ds, new Object[] { value } );
+        } catch (Exception e)  { throw wrap( e ); }   
+    }
+    private StandardException wrap( Throwable t )   { return StandardException.plainWrapException( t ); }
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // AUTHENTICATE LOCALLY
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * Authenticate the passed-in credentials against the local database.
+	 *
+	 * @param userName		The user's name used to connect to JBMS system
+	 * @param userPassword	The user's password used to connect to JBMS system
+	 * @param databaseName	The database which the user wants to connect to.
+	 */
+	private boolean	authenticateLocally
+        (
+         String userName,
+         String userPassword,
+         String databaseName
+         )
+        throws StandardException, SQLException
+	{
+        userName = IdUtil.getUserAuthorizationId( userName ) ;
+
+        TransactionController   tc = getTransaction();
+        try {
+            //
+            // Special bootstrap code. If we are creating a credentials database, then
+            // we store the DBO's initial credentials in it. We also turn on NATIVE LOCAL authentication
+            // forever.
+            //
+            if ( _creatingCredentialsDB )
+            {
+                _creatingCredentialsDB = false;
+            
+                SystemProcedures.addUser( userName, userPassword, tc );
+            
+                tc.setProperty
+                    ( Property.AUTHENTICATION_PROVIDER_PARAMETER, Property.AUTHENTICATION_PROVIDER_NATIVE_LOCAL, true );
+            
+                return true;
+            }
+        
+            //
+            // we expect to find a data dictionary
+            //
+            DataDictionary      dd = (DataDictionary) Monitor.getServiceModule( this, DataDictionary.MODULE );
+        
+            //
+            // NATIVE authentication is only available if the database is at version 10.9 or later
+            //
+            dd.checkVersion( DataDictionary.DD_VERSION_DERBY_10_9, "NATIVE AUTHENTICATION" );
+        
+            UserDescriptor      userDescriptor = dd.getUser( userName, tc );
+        
+            if ( userDescriptor == null )   { return false; }
+        
+            PasswordHasher      hasher = new PasswordHasher( userDescriptor.getHashingScheme() );
+            char[]                     candidatePassword = hasher.hashPasswordIntoString( userName, userPassword ).toCharArray();
+            char[]                     actualPassword = userDescriptor.getAndZeroPassword();
+        
+            if ( (candidatePassword == null) || (actualPassword == null)) { return false; }
+            if ( candidatePassword.length != actualPassword.length ) { return false; }
+        
+            for ( int i = 0; i < candidatePassword.length; i++ )
+            {
+                if ( candidatePassword[ i ] != actualPassword[ i ] ) { return false; }
+            }
+        
+            Arrays.fill( candidatePassword, (char) 0 );
+            Arrays.fill( actualPassword, (char) 0 );
+        
+            return true;
+        }
+        finally
+        {
+            tc.commit();
+        }
+    }
+    
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/NativeAuthenticationServiceImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/SpecificAuthenticationServiceImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/SpecificAuthenticationServiceImpl.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/SpecificAuthenticationServiceImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/authentication/SpecificAuthenticationServiceImpl.java Thu Jan 19 13:51:13 2012
@@ -68,6 +68,12 @@ public class SpecificAuthenticationServi
 		if (!requireAuthentication(properties))
 			return false;
 
+        //
+        // Don't treat the NATIVE authentication specification as a user-supplied
+        // class which should be instantiated.
+        //
+        if (  PropertyUtil.nativeAuthenticationEnabled( properties ) ) { return false; }
+
 		specificAuthenticationScheme = PropertyUtil.getPropertyFromSet(
 					properties,
 					org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_PARAMETER);
@@ -78,7 +84,7 @@ public class SpecificAuthenticationServi
 			  (!((StringUtil.SQLEqualsIgnoreCase(specificAuthenticationScheme,
 					  org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_BUILTIN)) ||
 			  (specificAuthenticationScheme.equalsIgnoreCase(
-					  org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_LDAP))  ))))
+                                                             org.apache.derby.iapi.reference.Property.AUTHENTICATION_PROVIDER_LDAP))  ))))
 			return true;
 		else
 			return false;

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/BaseMonitor.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/BaseMonitor.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/BaseMonitor.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/services/monitor/BaseMonitor.java Thu Jan 19 13:51:13 2012
@@ -392,6 +392,17 @@ abstract class BaseMonitor
 			bootPersistentServices( );
 	}
 
+    public  String  getCanonicalServiceName( String userSpecifiedName )
+        throws StandardException
+    {
+        if ( userSpecifiedName == null ) { return null; }
+        
+        PersistentService   correspondingService = findProviderForCreate(  userSpecifiedName );
+
+        if ( correspondingService == null ) { return null; }
+        else { return correspondingService.getCanonicalServiceName( userSpecifiedName ); }
+    }
+
 	public Object findService(String factoryInterface, String serviceName) {
 
 		if (serviceName == null)

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java Thu Jan 19 13:51:13 2012
@@ -522,7 +522,7 @@ public final class	DataDictionaryImpl
 			throws StandardException
 	{
 		softwareVersion = new DD_Version(this, DataDictionary.DD_VERSION_DERBY_10_9);
-        
+
 		startupParameters = startParams;
 
 		uuidFactory = Monitor.getMonitor().getUUIDFactory();
@@ -807,16 +807,22 @@ public final class	DataDictionaryImpl
                     DataDictionary.CREATE_DATA_DICTIONARY_VERSION,
                     dictionaryVersion, true);
 
-				// If SqlAuthorization is set as system property during database
-				// creation, set it as database property also, so it gets persisted.
-				if (PropertyUtil.getSystemBoolean(Property.SQL_AUTHORIZATION_PROPERTY))
+                boolean nativeAuthenticationEnabled = PropertyUtil.nativeAuthenticationEnabled( startParams );
+
+                //
+				// If SqlAuthorization is set as a system property during database
+				// creation, set it as a database property also, so that it gets persisted.
+                //
+                // We also turn on SqlAuthorization if NATIVE authentication has been specified.
+                //
+				if (PropertyUtil.getSystemBoolean(Property.SQL_AUTHORIZATION_PROPERTY) || nativeAuthenticationEnabled)
 				{
 					bootingTC.setProperty(Property.SQL_AUTHORIZATION_PROPERTY,"true",true);
 					usesSqlAuthorization=true;
 				}
 
                 // Set default hash algorithm used to protect passwords stored
-                // in the database for BUILTIN authentication.
+                // in the database for BUILTIN and NATIVE authentication.
                 bootingTC.setProperty(
                         Property.AUTHENTICATION_BUILTIN_ALGORITHM,
                         findDefaultBuiltinAlgorithm(),
@@ -7920,6 +7926,28 @@ public final class	DataDictionaryImpl
              );
 	}
 
+	public UserDescriptor getUser( String userName, TransactionController tc )
+		throws StandardException
+	{
+		ExecIndexRow				keyRow;
+		TabInfoImpl					ti = getNonCoreTI( SYSUSERS_CATALOG_NUM );
+
+		/* Set up the start/stop position for the scan */
+		keyRow = (ExecIndexRow) exFactory.getIndexableRow(1);
+		keyRow.setColumn( 1, new SQLVarchar( userName ) );
+
+		return (UserDescriptor) getDescriptorViaIndex
+            (
+             SYSUSERSRowFactory.SYSUSERS_INDEX1_ID,
+             keyRow,
+             (ScanQualifier [][]) null,
+             ti,
+             (TupleDescriptor) null,
+             (List) null,
+             false
+             );
+	}
+
 	public void dropUser( String userName, TransactionController tc )
 			throws StandardException
 	{	

Modified: db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml Thu Jan 19 13:51:13 2012
@@ -1239,6 +1239,17 @@ Guide.
            </msg>
 
             <msg>
+                <name>4251H</name>
+                <text>Invalid NATIVE authentication specification. Please set derby.authentication.provider to a value of the form NATIVE:$credentialsDB or NATIVE:$credentialsDB:LOCAL (at the system level) or NATIVE::LOCAL (at the database level).</text>
+           </msg>
+
+            <msg>
+                <name>4251I</name>
+                <text>Authentication cannot be performed because the credentials database '{0}' does not exist.</text>
+                 <arg>databaseName</arg>
+           </msg>
+
+            <msg>
                 <name>42601</name>
                 <text>In an ALTER TABLE statement, the column '{0}' has been specified as NOT NULL and either the DEFAULT clause was not specified or was specified as DEFAULT NULL.</text>
                 <arg>columnName</arg>

Modified: db/derby/code/trunk/java/engine/org/apache/derby/modules.properties
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/modules.properties?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/modules.properties (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/modules.properties Thu Jan 19 13:51:13 2012
@@ -205,6 +205,9 @@ cloudscape.config.NoneAuthentication=all
 # Authentication Service - Various Authentication Services/Schemes
 # (activated by derby.connection.requireAuthentication)
 #
+derby.module.nativeAuthentication=org.apache.derby.impl.jdbc.authentication.NativeAuthenticationServiceImpl
+cloudscape.config.nativeAuthentication=derby
+
 derby.module.basicAuthentication=org.apache.derby.impl.jdbc.authentication.BasicAuthenticationServiceImpl
 cloudscape.config.basicAuthentication=derby
 

Modified: db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java (original)
+++ db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java Thu Jan 19 13:51:13 2012
@@ -780,6 +780,8 @@ public interface SQLState {
 	String HIDDEN_COLUMN                                                         = "4251E";
 	String CANT_DROP_DBO                                                         = "4251F";
 	String WEAK_AUTHENTICATION                                               = "4251G";
+	String BAD_NATIVE_AUTH_SPEC                                               = "4251H";
+	String MISSING_CREDENTIALS_DB                                               = "4251I";
 
 	String LANG_DB2_NOT_NULL_COLUMN_INVALID_DEFAULT                    = "42601";
 	String LANG_DB2_INVALID_HEXADECIMAL_CONSTANT                    = "42606";

Modified: db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java (original)
+++ db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java Thu Jan 19 13:51:13 2012
@@ -684,10 +684,17 @@ public class EmptyDictionary implements 
 		// TODO Auto-generated method stub
     }
 
-	public void dropUser( String userName, TransactionController tc )
+	public void dropUser( String userName,TransactionController tc )
+		throws StandardException
+	{
+		// TODO Auto-generated method stub
+    }
+
+	public UserDescriptor getUser( String userName, TransactionController tc )
 			throws StandardException
 	{
 		// TODO Auto-generated method stub
+        return null;
     }
 
 	public int getEngineType() {

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthenticationServiceTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthenticationServiceTest.java?rev=1233377&view=auto
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthenticationServiceTest.java (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthenticationServiceTest.java Thu Jan 19 13:51:13 2012
@@ -0,0 +1,386 @@
+/*
+
+   Derby - Class org.apache.derbyTesting.functionTests.tests.lang.NativeAuthenticationServiceTest
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derbyTesting.functionTests.tests.lang;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.derbyTesting.junit.BaseJDBCTestCase;
+import org.apache.derbyTesting.junit.DatabaseChangeSetup;
+import org.apache.derbyTesting.junit.JDBC;
+import org.apache.derbyTesting.junit.TestConfiguration;
+import org.apache.derbyTesting.junit.SystemPropertyTestSetup;
+
+/**
+ * <p>
+ * Tests for the NATIVE authentication service introduced by DERBY-866.
+ * </p>
+ */
+public class NativeAuthenticationServiceTest extends GeneratedColumnsHelper
+{
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTANTS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    // fruits are legal users. nuts are not
+    private static  final   String  DBO = "KIWI";   
+    private static  final   String  APPLE_USER = "APPLE";   
+    private static  final   String  PEAR_USER = "PEAR";   
+
+    private static  final   String  WALNUT_USER = "WALNUT";
+
+    private static  final   String  CREDENTIALS_DB = "credDB";
+    private static  final   String  SECOND_DB = "secondDB";
+    private static  final   String  THIRD_DB = "thirdDB";
+
+    private static  final   String  PROVIDER_PROPERTY = "derby.authentication.provider";
+
+    private static  final   String  CREDENTIALS_DB_DOES_NOT_EXIST = "4251I";
+    private static  final   String  INVALID_AUTHENTICATION = "08004";
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // STATE
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    private final   boolean _nativeAuthentication;
+    private final   boolean _localAuthentication;
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTRUCTOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    public  NativeAuthenticationServiceTest
+        (
+         boolean    nativeAuthentication,
+         boolean    localAuthentication
+         )
+    {
+        super( "testAll" );
+
+        _nativeAuthentication = nativeAuthentication;
+        _localAuthentication = localAuthentication;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // SETUP BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>
+     * Return the system properties to be used in a particular test run.
+     * </p>
+     */
+    private Properties  systemProperties( String physicalDatabaseName )
+    {
+        if ( !_nativeAuthentication ) { return null; }
+
+        String  authenticationProvider = "NATIVE:" + physicalDatabaseName;
+        if ( _localAuthentication ) { authenticationProvider = authenticationProvider + ":LOCAL"; }
+
+        Properties  result = new Properties();
+        result.put( PROVIDER_PROPERTY, authenticationProvider );
+
+        return result;
+    }
+
+    /**
+     * <p>
+     * Construct the name of this test (useful for error messages).
+     * </p>
+     */
+    private String  nameOfTest()
+    {
+        String  authType = _nativeAuthentication ?
+            "NATIVE authentication on, " :
+            "Authentication off, ";
+        String  local = _localAuthentication ?
+            "LOCAL authentication ON" :
+            "LOCAL authentication OFF";
+
+        return "[ " + authType + local + " ]";
+    }
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // JUnit BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+
+    /**
+     * Construct top level suite in this JUnit test
+     */
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite();
+
+        suite.addTest( allConfigurations( false ) );
+        if ( !JDBC.vmSupportsJSR169() ) { suite.addTest( allConfigurations( true ) ); }
+
+        return suite;
+    }
+
+    /**
+     * <p>
+     * Create a suite of all test configurations.
+     * </p>
+     */
+    private static  Test   allConfigurations( boolean clientServer )
+    {
+        TestSuite suite = new TestSuite();
+
+        suite.addTest( decorate( new NativeAuthenticationServiceTest( false, false ), clientServer ) );
+        suite.addTest( decorate( new NativeAuthenticationServiceTest( true, true ), clientServer ) );
+        suite.addTest( decorate( new NativeAuthenticationServiceTest( true, false ), clientServer ) );
+
+        return suite;
+    }
+
+    /**
+     * <p>
+     * Wrap base test with standard decorators in order to setup system
+     * properties and allow for the creation of multiple databases with
+     * stored properties that can't be removed at tearDown time.
+     * </p>
+     */
+    private static  Test    decorate( NativeAuthenticationServiceTest nast, boolean clientServer )
+    {
+        String      credentialsDBPhysicalName = TestConfiguration.generateUniqueDatabaseName();
+        
+        Test        result = nast;
+
+        //
+        // Putting the clientServer decorator on the inside allows the server-side
+        // embedded driver to be re-registered after engine shutdown. If you put
+        // this decorator outside the SystemProperty decorator, then engine shutdown
+        // unregisters the server-side embedded driver and it can't be found by
+        // the next test.
+        //
+        if ( clientServer ) { result = TestConfiguration.clientServerDecorator( result ); }
+        
+        //
+        // Turn on the property which enables NATIVE authentication. This will trigger
+        // an engine shutdown at the end of the test. We want to shutdown the engine
+        // before deleting the physical databases. This is because we need one of the
+        // databases (the credentials db) in order to authenticate engine shutdown.
+        //
+        Properties  systemProperties = nast.systemProperties( credentialsDBPhysicalName );
+        if ( systemProperties != null )
+        {
+            result = new SystemPropertyTestSetup( result, systemProperties, true );
+        }
+        
+        //
+        // Register temporary databases, where the test will do its work.
+        // We can't use the default, re-usable database because NATIVE authentication stores
+        // persistent properties which cannot be turned off.
+        //
+        result = TestConfiguration.additionalDatabaseDecoratorNoShutdown
+            ( result, CREDENTIALS_DB, credentialsDBPhysicalName );
+        result = TestConfiguration.additionalDatabaseDecoratorNoShutdown( result, SECOND_DB );
+        result = TestConfiguration.additionalDatabaseDecoratorNoShutdown( result, THIRD_DB );
+
+        result = TestConfiguration.changeUserDecorator( result, DBO, getPassword( DBO ) );
+        
+        return result;
+    }
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // TESTS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>
+     * Entry point for tests.
+     * </p>
+     */
+    public  void    testAll()   throws Exception
+    {
+        println( nameOfTest() );
+
+        // can't create any database until the credentials db has been created
+        Connection  secondDBConn = createDB
+            ( _nativeAuthentication, SECOND_DB, APPLE_USER, CREDENTIALS_DB_DOES_NOT_EXIST );
+
+        // create the credentials database
+        Connection  sysadminConn = openConnection( CREDENTIALS_DB, DBO );
+
+        // add another legal user
+        addUser( sysadminConn, APPLE_USER );
+
+        //
+        // Creating the credentials db should have stored the following information in it:
+        //
+        // 1) The DBO's credentials should have been stored in SYSUSERS.
+        // 2) The authentication provider should have been set to NATIVE::LOCAL
+        // 3) SQL authorization should have been turned on.
+        //
+        String[][]  legalUsers = _nativeAuthentication ? new String[][] { { APPLE_USER }, { DBO } } : new String[][] {  { APPLE_USER } };
+        assertResults
+            (
+             sysadminConn,
+             "select username from sys.sysusers order by username",
+             legalUsers,
+             false
+             );
+        String[][]  authenticationProvider = _nativeAuthentication ? new String[][] { { "NATIVE::LOCAL" } } : new String[][] { { null } };
+        assertResults
+            (
+             sysadminConn,
+             "values ( syscs_util.syscs_get_database_property( 'derby.authentication.provider' ) )",
+             authenticationProvider,
+             false
+             );
+        String[][]  sqlAuthorization = _nativeAuthentication ? new String[][] { { "true" } } : new String[][] { { null } };
+        assertResults
+            (
+             sysadminConn,
+             "values ( syscs_util.syscs_get_database_property( 'derby.database.sqlAuthorization' ) )",
+             sqlAuthorization,
+             false
+             );
+
+        // Sanity-check that the creator of the credentials db is the DBO
+        String[][]   dboName = new String[][] { { DBO } };
+        assertResults
+            (
+             sysadminConn,
+             "select authorizationID from sys.sysschemas where schemaName = 'SYS'",
+             dboName,
+             false
+             );
+
+        // Databases can't be created by users who don't have credentials stored in the credentials database
+        Connection  thirdDBConn = createDB
+            ( _nativeAuthentication, THIRD_DB, WALNUT_USER, INVALID_AUTHENTICATION );
+
+        // Now let the other valid user create a database
+        if ( secondDBConn == null )
+        {
+            secondDBConn = createDB( false, SECOND_DB, APPLE_USER, null );
+        }
+
+        // verify that the other valid user is the dbo in the database he just created
+        assertResults
+            (
+             secondDBConn,
+             "select authorizationID from sys.sysschemas where schemaName = 'SYS'",
+             new String[][] { { APPLE_USER } },
+             false
+             );
+
+        // NATIVE authentication turns on SQL authorization in the second database
+        assertResults
+            (
+             secondDBConn,
+             "values ( syscs_util.syscs_get_database_property( 'derby.database.sqlAuthorization' ) )",
+             sqlAuthorization,
+             false
+             );
+
+        //
+        // If LOCAL authentication was specified...
+        //
+        // 1) It will be turned on in the second database too.
+        // 2) The other legal user's credentials (as the database dbo) will be stored.
+        //
+        authenticationProvider = _localAuthentication ? new String[][] { { "NATIVE::LOCAL" } } : new String[][] { { null } };
+        assertResults
+            (
+             secondDBConn,
+             "values ( syscs_util.syscs_get_database_property( 'derby.authentication.provider' ) )",
+             authenticationProvider,
+             false
+             );
+        legalUsers = _localAuthentication ? new String[][] { { APPLE_USER } } : new String[][] {};
+        assertResults
+            (
+             secondDBConn,
+             "select username from sys.sysusers order by username",
+             legalUsers,
+             false
+             );
+        
+    }
+
+    private Connection  createDB( boolean shouldFail, String dbName, String user, String expectedSQLState )
+        throws Exception
+    {
+        Connection  conn = null;
+
+        println( user + " attempting to create database " + dbName );
+
+        try {
+            conn = openConnection( dbName, user );
+
+            if ( shouldFail )   { fail( tagError( "Connection to " + dbName + " should have failed." ) ); }
+        }
+        catch (SQLException se)
+        {
+            if ( shouldFail )   { assertSQLState( expectedSQLState, se ); }
+            else    { fail( tagError( "Connection to " + dbName + " unexpectedly succeeded." ) );}
+        }
+
+        return conn;
+    }
+
+    private void    addUser( Connection conn, String user ) throws Exception
+    {
+        String  password = getPassword( user );
+        String  statement = "call syscs_util.syscs_create_user( '" + user + "', '" + password + "' )";
+        
+        goodStatement( conn, statement );
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // MINIONS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /** Open a connection to a database using the supplied credentials */
+    private Connection  openConnection( String logicalDBName, String user )
+        throws SQLException
+    {
+        return getTestConfiguration().openConnection( logicalDBName, user, getPassword( user ) );
+    }
+    
+    /** Get the password for a user */
+    private static  String  getPassword( String user ) { return user + "_password"; }
+
+    /** Tag an error with the name of the test configuration */
+    private String  tagError( String text ) { return nameOfTest() + ": " + text; }
+
+}

Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/NativeAuthenticationServiceTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/_Suite.java Thu Jan 19 13:51:13 2012
@@ -221,6 +221,7 @@ public class _Suite extends BaseTestCase
         suite.addTest(Derby5005Test.suite());
         suite.addTest(AutoIncrementTest.suite());
         suite.addTest(HalfCreatedDatabaseTest.suite());
+        suite.addTest(NativeAuthenticationServiceTest.suite());
         suite.addTest(NativeAuthProcs.suite());
         return suite;
 	}

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/BaseTestCase.java Thu Jan 19 13:51:13 2012
@@ -260,7 +260,7 @@ public abstract class BaseTestCase
      *
      * @param name name of the property
      */
-    protected static void removeSystemProperty(final String name)
+    public static void removeSystemProperty(final String name)
 	{
 	
 	AccessController.doPrivileged

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DatabaseChangeSetup.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DatabaseChangeSetup.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DatabaseChangeSetup.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DatabaseChangeSetup.java Thu Jan 19 13:51:13 2012
@@ -26,7 +26,7 @@ import junit.framework.Test;
  * Previous configuration is restored on tearDown.
  *
  */
-final class DatabaseChangeSetup extends ChangeConfigurationSetup {
+public  final class DatabaseChangeSetup extends ChangeConfigurationSetup {
 
     private final String logicalDbName;
     private final String dbName;
@@ -42,4 +42,7 @@ final class DatabaseChangeSetup extends 
     TestConfiguration getNewConfiguration(TestConfiguration old) {
         return new TestConfiguration(old, logicalDbName, dbName, defaultDb);
     }
+
+    public  String  physicalDatabaseName() { return dbName; }
+    
 }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DriverManagerConnector.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DriverManagerConnector.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DriverManagerConnector.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DriverManagerConnector.java Thu Jan 19 13:51:13 2012
@@ -80,7 +80,7 @@ public class DriverManagerConnector impl
         try {
             return DriverManager.getConnection(url, connectionAttributes);
         } catch (SQLException e) {
-            
+
             // Expected state for database not found.
             // For the client the generic 08004 is returned,
             // will just retry on that.
@@ -95,6 +95,7 @@ public class DriverManagerConnector impl
             
             Properties attributes = new Properties(connectionAttributes);
             attributes.setProperty("create", "true");
+
             return DriverManager.getConnection(url, attributes);
         }
     }
@@ -117,7 +118,7 @@ public class DriverManagerConnector impl
      */
     public void shutEngine() throws SQLException {
         
-        getConnectionByAttributes("jdbc:derby:", "shutdown", "true");        
+        getConnectionByAttributes("jdbc:derby:", "shutdown", "true");
     }
     
     /**
@@ -129,7 +130,7 @@ public class DriverManagerConnector impl
         throws SQLException
     {
         Properties attributes = new Properties();
-        
+
         attributes.setProperty("user", config.getUserName());
         attributes.setProperty("password", config.getUserPassword());
         attributes.setProperty(key, value);

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DropDatabaseSetup.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DropDatabaseSetup.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DropDatabaseSetup.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/DropDatabaseSetup.java Thu Jan 19 13:51:13 2012
@@ -54,12 +54,13 @@ class DropDatabaseSetup extends BaseTest
             config.openConnection(logicalDBName).close();
             shutdown = true;
         } catch (SQLException e) {
+            String  sqlState = e.getSQLState();
             // If the database cannot be booted due
             // to some restrictions such as authentication
             // or encrypted (ie here we don't know the 
             // correct authentication tokens, then it's
             // ok since we just want it shutdown anyway!
-            if ("XJ040".equals(e.getSQLState()))
+            if ( "XJ040".equals( sqlState ) || "08004".equals( sqlState ) || "4251I".equals( sqlState ) )
             {
                 shutdown = false;
             }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/NetworkServerTestSetup.java Thu Jan 19 13:51:13 2012
@@ -31,6 +31,7 @@ import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
+import java.sql.SQLException;
 import java.util.ArrayList;
 import junit.framework.Test;
 import org.apache.derby.drda.NetworkServerControl;
@@ -464,9 +465,20 @@ final public class NetworkServerTestSetu
             if (failedShutdown != null)
             {
                 if (failedShutdown instanceof Exception)
-                    throw (Exception) failedShutdown;
-                
-                throw (Error) failedShutdown;
+                {
+                    // authentication failure is ok.
+                    if (
+                        !(failedShutdown instanceof SQLException) ||
+                        !( "4251I".equals( ((SQLException) failedShutdown).getSQLState() ) )
+                        )
+                    {
+                        throw (Exception) failedShutdown;
+                    }
+                }
+                else
+                {
+                    throw (Error) failedShutdown;
+                }
             }
                 
         }

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SystemPropertyTestSetup.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SystemPropertyTestSetup.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SystemPropertyTestSetup.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/SystemPropertyTestSetup.java Thu Jan 19 13:51:13 2012
@@ -77,10 +77,14 @@ public class SystemPropertyTestSetup ext
     protected void setUp()
     throws java.lang.Exception
     {
-    	setProperties(newValues);
     	// shutdown engine so static properties take effect
+        // shutdown the engine before setting the properties. this
+        // is because the properties may change authentication settings
+        // to NATIVE authentication and we may be missing a credentials DB.
     	if (staticProperties)
-    		TestConfiguration.getCurrent().shutdownEngine();
+    	{ TestConfiguration.getCurrent().shutdownEngine(); }
+        
+    	setProperties(newValues);
     }
 
     /**

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java?rev=1233377&r1=1233376&r2=1233377&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java Thu Jan 19 13:51:13 2012
@@ -669,7 +669,7 @@ public final class TestConfiguration {
     /**
      * Generate the unique database name for single use.
      */
-    private static synchronized String generateUniqueDatabaseName()
+    public static synchronized String generateUniqueDatabaseName()
     {
         // Forward slash is ok, Derby treats database names
         // as URLs and translates forward slash to the local
@@ -766,7 +766,7 @@ public final class TestConfiguration {
      * the database in openConnection(String logicalDatabaseName) method calls.
      * @return decorated test.
      */
-    public static TestSetup additionalDatabaseDecorator(Test test, String logicalDbName)
+    public static DatabaseChangeSetup additionalDatabaseDecorator(Test test, String logicalDbName)
     {
         return new DatabaseChangeSetup(new DropDatabaseSetup(test, logicalDbName),
                                        logicalDbName,
@@ -786,7 +786,7 @@ public final class TestConfiguration {
      *                      method calls.
      * @return decorated test.
      */
-    public static TestSetup additionalDatabaseDecoratorNoShutdown(
+    public static DatabaseChangeSetup additionalDatabaseDecoratorNoShutdown(
         Test test,
         String logicalDbName)
     {
@@ -804,6 +804,36 @@ public final class TestConfiguration {
     }
 
     /**
+     * Similar to additionalDatabaseDecorator except the database will
+     * not be shutdown, only deleted. It is the responsibility of the
+     * test to shut it down.
+     *
+     * @param test Test to be decorated
+     * @param logicalDbName The logical database name. This name is
+     *                      used to identify the database in
+     *                      openConnection(String logicalDatabaseName)
+     *                      method calls.
+     * @param physicalDbName - Real database name on disk.
+     * @return decorated test.
+     */
+    public static DatabaseChangeSetup additionalDatabaseDecoratorNoShutdown(
+        Test test,
+        String logicalDbName, String physicalDbName )
+    {
+        return new DatabaseChangeSetup(
+            new DropDatabaseSetup(test, logicalDbName)
+            {
+                protected void tearDown() throws Exception {
+                    // the test is responsible for shutdown
+                    removeDatabase();
+                }
+            },
+            logicalDbName,
+            physicalDbName,
+            false);
+    }
+
+    /**
      * Decorate a test changing the default user name and password.
      * Typically used along with DatabasePropertyTestSetup.builtinAuthentication.
      * The tearDown method resets the default user and password value to
@@ -1381,8 +1411,7 @@ public final class TestConfiguration {
     public JDBCClient getJDBCClient() {
         return jdbcClient;
     }
-    
-    
+
     /**
      * <p>
      * Return the jdbc url for connecting to the default database.
@@ -1597,14 +1626,43 @@ public final class TestConfiguration {
      * @return connection to specified database.
      */
     Connection openConnection(String logicalDatabaseName)
-        throws SQLException {
-        String databaseName = getPhysicalDatabaseName(logicalDatabaseName);
-        if (usedDbNames.contains(databaseName))
-            return connector.openConnection(databaseName);
+        throws SQLException
+    {
+        return connector.openConnection( getAndVetPhysicalDatabaseName( logicalDatabaseName ) );
+    }
+    private String  getAndVetPhysicalDatabaseName( String logicalDatabaseName )
+        throws SQLException
+    {
+        String databaseName = getPhysicalDatabaseName( logicalDatabaseName );
+        
+        if ( usedDbNames.contains(databaseName) ) { return databaseName; }
         else
+        {
             throw new SQLException("Database name \"" + logicalDatabaseName
                       + "\" is not in a list of used databases."
                       + "Use method TestConfiguration.additionalDatabaseDecorator first.");
+        }
+    }
+
+    /**
+     * Open connection to the specified database using the supplied username and password.
+     * If the database does not exist, it will be created.
+     * Requires that the test has been decorated with
+     * additionalDatabaseDecorator with the matching name.
+     * The physical database name may differ.
+     * @param logicalDatabaseName A logical database name as passed
+     * to <code>additionalDatabaseDecorator</code> function.
+     * @return connection to specified database.
+     */
+    public  Connection openConnection( String logicalDatabaseName, String user, String password )
+        throws SQLException
+    {
+        return connector.openConnection
+            (
+             getAndVetPhysicalDatabaseName( logicalDatabaseName ),
+             user,
+             password
+             );
     }
 
     /**