From 3bfd09e5302fcd2511bcbd705e2c9dedaa44e142 Mon Sep 17 00:00:00 2001 From: "Bobryk-Ozaki, Terrence" Date: Mon, 26 Oct 2026 09:42:09 -0700 Subject: [PATCH] fix(security): blank Quartz's placeholder H2 admin/admin datasource credential quartz.properties shipped with a real-looking hardcoded H2 datasource credential (user=admin, password=admin, URL pointing at a developer's own Windows machine path), despite SchedulerFactorySingleton.init() always routing through setUpQuartzProperties(), which unconditionally overwrites driver/URL/user/password with the actual scheduler database's own connection details before Quartz's StdSchedulerFactory is ever initialized - confirmed by direct trace of both call sites in init() (the IEmbeddedRDBMSServerEngine and RDBMSNativeEngine branches) and the single setUpQuartzProperties() implementation they share. This fails open rather than closed: Quartz would simply run with the weak default silently if the override logic were ever changed to skip it, since there's no equivalent of a fail-closed ConfigurationException. Blanking the four placeholder values instead means a future regression in the override logic would surface as an obvious connection failure. Added SchedulerFactorySingletonUnitTests to confirm, using the real project quartz.properties copied into the test's base folder exactly as semosshome.xml packages it for deployment: the four placeholders are blank (not "admin") in the file itself, setUpQuartzProperties() correctly overwrites all four with real connection details regardless, and the non-connection settings (jobStore.class, tablePrefix) still load from the file as before. Co-Authored-By: Claude Sonnet 5 --- quartz.properties | 20 ++-- .../SchedulerFactorySingletonUnitTests.java | 98 +++++++++++++++++++ 2 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 test/prerna/reactor/scheduler/SchedulerFactorySingletonUnitTests.java diff --git a/quartz.properties b/quartz.properties index fdeefee984f..1721f83a90a 100644 --- a/quartz.properties +++ b/quartz.properties @@ -22,12 +22,18 @@ org.quartz.jobStore.dataSource=myDS org.quartz.jobStore.useProperties=false org.quartz.jobStore.isClustered=false -# persist to H2 database at path '/tmp/MyScheduler.quartz.h2' -org.quartz.dataSource.myDS.driver = org.h2.Driver - -#Quartz dataSource -org.quartz.dataSource.myDS.URL=jdbc:h2:tcp://localhost:9092/C:/workspace/Semoss_Dev/quartz/db3 -org.quartz.dataSource.myDS.user=admin -org.quartz.dataSource.myDS.password=admin +# driver/URL/user/password below are template-only placeholders: every actual +# Quartz initialization goes through SchedulerFactorySingleton.setUpQuartzProperties(), +# which loads this file then unconditionally overwrites all four with the real +# scheduler database's own driver/connection URL/credentials before the factory +# is ever initialized. Left as a real-looking admin/admin H2-on-localhost value, +# this reads as a live hardcoded credential to any secret scanner even though it +# is never actually used; left blank instead, a future change that accidentally +# stops overriding these would fail obviously (Quartz cannot connect) rather than +# silently running with a weak default. +org.quartz.dataSource.myDS.driver= +org.quartz.dataSource.myDS.URL= +org.quartz.dataSource.myDS.user= +org.quartz.dataSource.myDS.password= org.quartz.dataSource.myDS.maxConnections=5 org.quartz.dataSource.myDS.validationQuery=select 1 \ No newline at end of file diff --git a/test/prerna/reactor/scheduler/SchedulerFactorySingletonUnitTests.java b/test/prerna/reactor/scheduler/SchedulerFactorySingletonUnitTests.java new file mode 100644 index 00000000000..753aadb7353 --- /dev/null +++ b/test/prerna/reactor/scheduler/SchedulerFactorySingletonUnitTests.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright 2015 Defense Health Agency (DHA) + * + * If your use of this software does not include any GPLv2 components: + * Licensed 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. + * ---------------------------------------------------------------------------- + * If your use of this software includes any GPLv2 components: + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *******************************************************************************/ +package prerna.reactor.scheduler; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.Properties; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import prerna.SemossUnitTest; +import prerna.util.sql.RdbmsTypeEnum; + +/** + * quartz.properties ships with blank driver/URL/user/password placeholders + * (see the comment above them in that file) since SchedulerFactorySingleton + * always overwrites those four with the real scheduler database's own + * connection details before Quartz is initialized. This confirms that + * override actually happens, using the real project quartz.properties copied + * into the test's base folder exactly as the deployed semosshome packaging + * does (per semosshome.xml). + */ +class SchedulerFactorySingletonUnitTests extends SemossUnitTest { + + @BeforeAll + static void copyQuartzProperties() throws IOException { + Path projectRoot = Paths.get("").toAbsolutePath(); + Path source = projectRoot.resolve("quartz.properties"); + Files.copy(source, semossDir.resolve("quartz.properties"), StandardCopyOption.REPLACE_EXISTING); + } + + @Test + void realConnectionDetailsOverwriteThePlaceholders() { + Properties result = SchedulerFactorySingleton.setUpQuartzProperties(null, "jdbc:h2:mem:realtest", "realuser", + "realpass", RdbmsTypeEnum.H2_DB); + + assertEquals("jdbc:h2:mem:realtest", result.getProperty("org.quartz.dataSource.myDS.URL")); + assertEquals("org.h2.Driver", result.getProperty("org.quartz.dataSource.myDS.driver")); + assertEquals("realuser", result.getProperty("org.quartz.dataSource.myDS.user")); + assertEquals("realpass", result.getProperty("org.quartz.dataSource.myDS.password")); + assertEquals("org.quartz.impl.jdbcjobstore.StdJDBCDelegate", + result.getProperty("org.quartz.jobStore.driverDelegateClass")); + } + + @Test + void placeholdersInTheFileItselfAreBlankNotARealCredential() throws IOException { + Properties raw = new Properties(); + try (var in = Files.newInputStream(semossDir.resolve("quartz.properties"))) { + raw.load(in); + } + + assertTrue(raw.getProperty("org.quartz.dataSource.myDS.user", "").isEmpty()); + assertTrue(raw.getProperty("org.quartz.dataSource.myDS.password", "").isEmpty()); + assertFalse(raw.getProperty("org.quartz.dataSource.myDS.password", "").equals("admin")); + } + + @Test + void nonConnectionSettingsStillLoadFromTheFile() { + Properties result = SchedulerFactorySingleton.setUpQuartzProperties(null, "jdbc:h2:mem:realtest", "realuser", + "realpass", RdbmsTypeEnum.H2_DB); + + assertEquals("org.quartz.impl.jdbcjobstore.JobStoreTX", result.getProperty("org.quartz.jobStore.class")); + assertEquals("QRTZ_", result.getProperty("org.quartz.jobStore.tablePrefix")); + } +}