From 079a4944e322cd0e5a5b5e8c8beb58ab5ccd881a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87etin?= Date: Tue, 12 Aug 2025 14:31:30 +0300 Subject: [PATCH 1/3] feat(oracle): add separate system password and withOraclePassword API; fix ORACLE_PASSWORD mapping Introduce an independent system user password for Oracle containers and map it correctly to the ORACLE_PASSWORD env. - Add withOraclePassword(String) to configure SYSTEM/SYS password independently from the app user password - Use system password in SID connections (getPassword returns system password in SID mode) - Keep backward compatibility: withPassword also sets system password unless withOraclePassword was called - Update Oracle XE and Oracle Free containers to use the new internal oraclePassword - Add OracleContainerTest to verify system vs app password behavior in SID and non-SID modes This resolves the issue where ORACLE_PASSWORD was effectively bound to the app user password, making it impossible to set the system user password correctly. --- .../oracle/OracleContainer.java | 39 +++++++++++++++++- .../junit/oracle/SimpleOracleTest.java | 40 ++++++++++++++++++- .../containers/OracleContainer.java | 39 +++++++++++++++++- .../junit/oracle/SimpleOracleTest.java | 40 ++++++++++++++++++- 4 files changed, 150 insertions(+), 8 deletions(-) diff --git a/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java b/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java index 2080c24283b..bb5ce250f70 100644 --- a/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java +++ b/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java @@ -58,6 +58,18 @@ public class OracleContainer extends JdbcDatabaseContainer { private String password = APP_USER_PASSWORD; + /** + * Password for Oracle system user (e.g. SYSTEM/SYS). Defaults to {@link #APP_USER_PASSWORD} + * for backwards compatibility, but can be customized independently via {@link #withOraclePassword(String)}. + */ + private String oraclePassword = APP_USER_PASSWORD; + + /** + * Tracks whether {@link #withOraclePassword(String)} was called to avoid overriding + * the system password when {@link #withPassword(String)} is used for the application user only. + */ + private boolean oraclePasswordExplicitlySet = false; + private boolean usingSid = false; public OracleContainer(String dockerImageName) { @@ -112,7 +124,8 @@ public String getUsername() { @Override public String getPassword() { - return password; + // When connecting via SID we authenticate as SYSTEM. Use the dedicated system password. + return isUsingSid() ? oraclePassword : password; } @Override @@ -142,6 +155,27 @@ public OracleContainer withPassword(String password) { throw new IllegalArgumentException("Password cannot be null or empty"); } this.password = password; + // Maintain backwards compatibility: if oracle password wasn't set explicitly, + // align it with the application user's password. + if (!oraclePasswordExplicitlySet) { + this.oraclePassword = password; + } + return self(); + } + + /** + * Sets the password for the Oracle system user (SYSTEM/SYS). This is independent from the + * application user password set via {@link #withPassword(String)}. + * + * @param oraclePassword password for SYSTEM/SYS users inside the container + * @return this container instance + */ + public OracleContainer withOraclePassword(String oraclePassword) { + if (StringUtils.isEmpty(oraclePassword)) { + throw new IllegalArgumentException("Oracle password cannot be null or empty"); + } + this.oraclePassword = oraclePassword; + this.oraclePasswordExplicitlySet = true; return self(); } @@ -185,7 +219,8 @@ public String getTestQueryString() { @Override protected void configure() { - withEnv("ORACLE_PASSWORD", password); + // Configure system user password independently from application user's password + withEnv("ORACLE_PASSWORD", oraclePassword); // Only set ORACLE_DATABASE if different than the default. if (databaseName != DEFAULT_DATABASE_NAME) { diff --git a/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java b/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java index 45ed72cb622..e50e63ca74d 100644 --- a/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java +++ b/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java @@ -31,6 +31,20 @@ private void runTest(OracleContainer container, String databaseName, String user assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); } + private void runTestSystemUser(OracleContainer container, String databaseName, String username, String password) + throws SQLException { + //Test config was honored + assertThat(container.getDatabaseName()).isEqualTo(databaseName); + assertThat(container.getUsername()).isEqualTo(username); + assertThat(container.getPassword()).isEqualTo(password); + + //Test we can get a connection and execute a system-level command + container.start(); + ResultSet resultSet = performQuery(container, "GRANT DBA TO " + username); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic system user query succeeds").isEqualTo(1); + } + @Test public void testDefaultSettings() throws SQLException { try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME)) { @@ -77,7 +91,7 @@ public void testCustomUser() throws SQLException { @Test public void testSID() throws SQLException { try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME).usingSid()) { - runTest(oracle, "freepdb1", "system", "test"); + runTestSystemUser(oracle, "freepdb1", "system", "test"); // Match against the last ':' String urlSuffix = oracle.getJdbcUrl().split("(\\:)(?!.*\\:)", 2)[1]; @@ -92,7 +106,29 @@ public void testSIDAndCustomPassword() throws SQLException { .usingSid() .withPassword("testPassword") ) { - runTest(oracle, "freepdb1", "system", "testPassword"); + runTestSystemUser(oracle, "freepdb1", "system", "testPassword"); + } + } + + @Test + public void testSeparateSystemAndAppPasswords() throws SQLException { + // SID mode should use system password + try ( + OracleContainer oracleSid = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .usingSid() + .withOraclePassword("SysP@ss1!") + .withPassword("AppP@ss1!") + ) { + runTestSystemUser(oracleSid, "freepdb1", "system", "SysP@ss1!"); + } + + // Non-SID mode should use application user's password + try ( + OracleContainer oraclePdb = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .withOraclePassword("SysP@ss2!") + .withPassword("AppP@ss2!") + ) { + runTest(oraclePdb, "freepdb1", "test", "AppP@ss2!"); } } diff --git a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java index 07db62a7b2e..bf1f0ea53e1 100644 --- a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java +++ b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java @@ -61,6 +61,18 @@ public class OracleContainer extends JdbcDatabaseContainer { private String password = APP_USER_PASSWORD; + /** + * Password for Oracle system user (e.g. SYSTEM/SYS). Defaults to {@link #APP_USER_PASSWORD} + * for backwards compatibility, but can be customized independently via {@link #withOraclePassword(String)}. + */ + private String oraclePassword = APP_USER_PASSWORD; + + /** + * Tracks whether {@link #withOraclePassword(String)} was called to avoid overriding + * the system password when {@link #withPassword(String)} is used for the application user only. + */ + private boolean oraclePasswordExplicitlySet = false; + private boolean usingSid = false; public OracleContainer(String dockerImageName) { @@ -125,7 +137,8 @@ public String getUsername() { @Override public String getPassword() { - return password; + // When connecting via SID we authenticate as SYSTEM. Use the dedicated system password. + return isUsingSid() ? oraclePassword : password; } @Override @@ -155,6 +168,27 @@ public OracleContainer withPassword(String password) { throw new IllegalArgumentException("Password cannot be null or empty"); } this.password = password; + // Maintain backwards compatibility: if oracle password wasn't set explicitly, + // align it with the application user's password. + if (!oraclePasswordExplicitlySet) { + this.oraclePassword = password; + } + return self(); + } + + /** + * Sets the password for the Oracle system user (SYSTEM/SYS). This is independent from the + * application user password set via {@link #withPassword(String)}. + * + * @param oraclePassword password for SYSTEM/SYS users inside the container + * @return this container instance + */ + public OracleContainer withOraclePassword(String oraclePassword) { + if (StringUtils.isEmpty(oraclePassword)) { + throw new IllegalArgumentException("Oracle password cannot be null or empty"); + } + this.oraclePassword = oraclePassword; + this.oraclePasswordExplicitlySet = true; return self(); } @@ -203,7 +237,8 @@ public String getTestQueryString() { @Override protected void configure() { - withEnv("ORACLE_PASSWORD", password); + // Configure system user password independently from application user's password + withEnv("ORACLE_PASSWORD", oraclePassword); // Only set ORACLE_DATABASE if different than the default. if (databaseName != DEFAULT_DATABASE_NAME) { diff --git a/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java b/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java index 82ef9846ab2..6c7a561b871 100644 --- a/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java +++ b/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java @@ -31,6 +31,20 @@ private void runTest(OracleContainer container, String databaseName, String user assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); } + private void runTestSystemUser(OracleContainer container, String databaseName, String username, String password) + throws SQLException { + //Test config was honored + assertThat(container.getDatabaseName()).isEqualTo(databaseName); + assertThat(container.getUsername()).isEqualTo(username); + assertThat(container.getPassword()).isEqualTo(password); + + //Test we can get a connection and execute a system-level command + container.start(); + ResultSet resultSet = performQuery(container, "GRANT DBA TO " + username); + int resultSetInt = resultSet.getInt(1); + assertThat(resultSetInt).as("A basic system user query succeeds").isEqualTo(1); + } + @Test public void testDefaultSettings() throws SQLException { try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME);) { @@ -77,7 +91,7 @@ public void testCustomUser() throws SQLException { @Test public void testSID() throws SQLException { try (OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME).usingSid();) { - runTest(oracle, "xepdb1", "system", "test"); + runTestSystemUser(oracle, "xepdb1", "system", "test"); // Match against the last ':' String urlSuffix = oracle.getJdbcUrl().split("(\\:)(?!.*\\:)", 2)[1]; @@ -92,7 +106,29 @@ public void testSIDAndCustomPassword() throws SQLException { .usingSid() .withPassword("testPassword"); ) { - runTest(oracle, "xepdb1", "system", "testPassword"); + runTestSystemUser(oracle, "xepdb1", "system", "testPassword"); + } + } + + @Test + public void testSeparateSystemAndAppPasswords() throws SQLException { + // SID mode should use system password + try ( + OracleContainer oracleSid = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .usingSid() + .withOraclePassword("SysP@ss1!") + .withPassword("AppP@ss1!") + ) { + runTestSystemUser(oracleSid, "xepdb1", "system", "SysP@ss1!"); + } + + // Non-SID mode should use application user's password + try ( + OracleContainer oraclePdb = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .withOraclePassword("SysP@ss2!") + .withPassword("AppP@ss2!") + ) { + runTest(oraclePdb, "xepdb1", "test", "AppP@ss2!"); } } From 6e0ac921d092e25ea976726dc9e60fd6f9be96d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87etin?= Date: Fri, 26 Sep 2025 15:10:55 +0300 Subject: [PATCH 2/3] refactor (OracleContainer): withOraclePassword rename to withSystemPassword refactor (OracleContainer): oraclePassword rename to systemPassword refactor (OracleContainer): oraclePasswordExplicitlySet rename to systemPasswordExplicitlySet --- .../oracle/OracleContainer.java | 14 ++++++------ .../junit/oracle/SimpleOracleTest.java | 19 ++++++++-------- .../containers/OracleContainer.java | 22 +++++++++---------- .../junit/oracle/SimpleOracleTest.java | 7 ++---- 4 files changed, 30 insertions(+), 32 deletions(-) diff --git a/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java b/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java index bb5ce250f70..396f6e41223 100644 --- a/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java +++ b/modules/oracle-free/src/main/java/org/testcontainers/oracle/OracleContainer.java @@ -60,15 +60,15 @@ public class OracleContainer extends JdbcDatabaseContainer { /** * Password for Oracle system user (e.g. SYSTEM/SYS). Defaults to {@link #APP_USER_PASSWORD} - * for backwards compatibility, but can be customized independently via {@link #withOraclePassword(String)}. + * for backwards compatibility, but can be customized independently via {@link #withSystemPassword(String)}. */ private String oraclePassword = APP_USER_PASSWORD; /** - * Tracks whether {@link #withOraclePassword(String)} was called to avoid overriding + * Tracks whether {@link #withSystemPassword(String)} was called to avoid overriding * the system password when {@link #withPassword(String)} is used for the application user only. */ - private boolean oraclePasswordExplicitlySet = false; + private boolean systemPasswordExplicitlySet = false; private boolean usingSid = false; @@ -155,9 +155,9 @@ public OracleContainer withPassword(String password) { throw new IllegalArgumentException("Password cannot be null or empty"); } this.password = password; - // Maintain backwards compatibility: if oracle password wasn't set explicitly, + // Maintain backwards compatibility: if system password wasn't set explicitly, // align it with the application user's password. - if (!oraclePasswordExplicitlySet) { + if (!systemPasswordExplicitlySet) { this.oraclePassword = password; } return self(); @@ -170,12 +170,12 @@ public OracleContainer withPassword(String password) { * @param oraclePassword password for SYSTEM/SYS users inside the container * @return this container instance */ - public OracleContainer withOraclePassword(String oraclePassword) { + public OracleContainer withSystemPassword(String oraclePassword) { if (StringUtils.isEmpty(oraclePassword)) { throw new IllegalArgumentException("Oracle password cannot be null or empty"); } this.oraclePassword = oraclePassword; - this.oraclePasswordExplicitlySet = true; + this.systemPasswordExplicitlySet = true; return self(); } diff --git a/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java b/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java index 74024397c5f..6cd076ad30f 100644 --- a/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java +++ b/modules/oracle-free/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java @@ -112,24 +112,25 @@ void testSIDAndCustomPassword() throws SQLException { } @Test - public void testSeparateSystemAndAppPasswords() throws SQLException { - // SID mode should use system password + public void testWithSystemPassword() throws SQLException { try ( - OracleContainer oracleSid = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) .usingSid() - .withOraclePassword("SysP@ss1!") + .withSystemPassword("SysP@ss1!") .withPassword("AppP@ss1!") ) { - runTestSystemUser(oracleSid, "freepdb1", "system", "SysP@ss1!"); + runTestSystemUser(oracle, "freepdb1", "system", "SysP@ss1!"); } + } - // Non-SID mode should use application user's password + @Test + public void testWithPassword() throws SQLException { try ( - OracleContainer oraclePdb = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) - .withOraclePassword("SysP@ss2!") + OracleContainer oracle = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) + .withSystemPassword("SysP@ss2!") .withPassword("AppP@ss2!") ) { - runTest(oraclePdb, "freepdb1", "test", "AppP@ss2!"); + runTest(oracle, "freepdb1", "test", "AppP@ss2!"); } } diff --git a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java index bf1f0ea53e1..1ecf6c2beba 100644 --- a/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java +++ b/modules/oracle-xe/src/main/java/org/testcontainers/containers/OracleContainer.java @@ -63,15 +63,15 @@ public class OracleContainer extends JdbcDatabaseContainer { /** * Password for Oracle system user (e.g. SYSTEM/SYS). Defaults to {@link #APP_USER_PASSWORD} - * for backwards compatibility, but can be customized independently via {@link #withOraclePassword(String)}. + * for backwards compatibility, but can be customized independently via {@link #withSystemPassword(String)}. */ - private String oraclePassword = APP_USER_PASSWORD; + private String systemPassword = APP_USER_PASSWORD; /** - * Tracks whether {@link #withOraclePassword(String)} was called to avoid overriding + * Tracks whether {@link #withSystemPassword(String)} was called to avoid overriding * the system password when {@link #withPassword(String)} is used for the application user only. */ - private boolean oraclePasswordExplicitlySet = false; + private boolean systemPasswordExplicitlySet = false; private boolean usingSid = false; @@ -138,7 +138,7 @@ public String getUsername() { @Override public String getPassword() { // When connecting via SID we authenticate as SYSTEM. Use the dedicated system password. - return isUsingSid() ? oraclePassword : password; + return isUsingSid() ? systemPassword : password; } @Override @@ -170,8 +170,8 @@ public OracleContainer withPassword(String password) { this.password = password; // Maintain backwards compatibility: if oracle password wasn't set explicitly, // align it with the application user's password. - if (!oraclePasswordExplicitlySet) { - this.oraclePassword = password; + if (!systemPasswordExplicitlySet) { + this.systemPassword = password; } return self(); } @@ -183,12 +183,12 @@ public OracleContainer withPassword(String password) { * @param oraclePassword password for SYSTEM/SYS users inside the container * @return this container instance */ - public OracleContainer withOraclePassword(String oraclePassword) { + public OracleContainer withSystemPassword(String oraclePassword) { if (StringUtils.isEmpty(oraclePassword)) { throw new IllegalArgumentException("Oracle password cannot be null or empty"); } - this.oraclePassword = oraclePassword; - this.oraclePasswordExplicitlySet = true; + this.systemPassword = oraclePassword; + this.systemPasswordExplicitlySet = true; return self(); } @@ -238,7 +238,7 @@ public String getTestQueryString() { @Override protected void configure() { // Configure system user password independently from application user's password - withEnv("ORACLE_PASSWORD", oraclePassword); + withEnv("ORACLE_PASSWORD", systemPassword); // Only set ORACLE_DATABASE if different than the default. if (databaseName != DEFAULT_DATABASE_NAME) { diff --git a/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java b/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java index f11bb3106d3..4eb224262d0 100644 --- a/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java +++ b/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java @@ -33,12 +33,10 @@ private void runTest(OracleContainer container, String databaseName, String user private void runTestSystemUser(OracleContainer container, String databaseName, String username, String password) throws SQLException { - //Test config was honored assertThat(container.getDatabaseName()).isEqualTo(databaseName); assertThat(container.getUsername()).isEqualTo(username); assertThat(container.getPassword()).isEqualTo(password); - //Test we can get a connection and execute a system-level command container.start(); ResultSet resultSet = performQuery(container, "GRANT DBA TO " + username); int resultSetInt = resultSet.getInt(1); @@ -113,11 +111,10 @@ void testSIDAndCustomPassword() throws SQLException { @Test public void testSeparateSystemAndAppPasswords() throws SQLException { - // SID mode should use system password try ( OracleContainer oracleSid = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) .usingSid() - .withOraclePassword("SysP@ss1!") + .withSystemPassword("SysP@ss1!") .withPassword("AppP@ss1!") ) { runTestSystemUser(oracleSid, "xepdb1", "system", "SysP@ss1!"); @@ -126,7 +123,7 @@ public void testSeparateSystemAndAppPasswords() throws SQLException { // Non-SID mode should use application user's password try ( OracleContainer oraclePdb = new OracleContainer(ORACLE_DOCKER_IMAGE_NAME) - .withOraclePassword("SysP@ss2!") + .withSystemPassword("SysP@ss2!") .withPassword("AppP@ss2!") ) { runTest(oraclePdb, "xepdb1", "test", "AppP@ss2!"); From 63752c551312d22aebe3398837f61c83678813f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87etin?= Date: Thu, 20 Nov 2025 15:28:18 +0300 Subject: [PATCH 3/3] fix (test)(oracle-xe): update test to verify current user instead of DBA grant The test now checks the connected session's user rather than verifying DBA grant status, which provides a more direct validation of the system user connection. --- .../org/testcontainers/junit/oracle/SimpleOracleTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java b/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java index 4eb224262d0..fd2e1602afb 100644 --- a/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java +++ b/modules/oracle-xe/src/test/java/org/testcontainers/junit/oracle/SimpleOracleTest.java @@ -38,9 +38,11 @@ private void runTestSystemUser(OracleContainer container, String databaseName, S assertThat(container.getPassword()).isEqualTo(password); container.start(); - ResultSet resultSet = performQuery(container, "GRANT DBA TO " + username); - int resultSetInt = resultSet.getInt(1); - assertThat(resultSetInt).as("A basic system user query succeeds").isEqualTo(1); + ResultSet resultSet = performQuery(container, "SELECT USER FROM DUAL"); + String currentUser = resultSet.getString(1); + assertThat(currentUser) + .as("Connected session should run as the system user") + .isEqualToIgnoringCase(username); } @Test