This is an automated email from the ASF dual-hosted git repository. ashishvijaywargiya pushed a commit to branch secret-manager in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git
commit 9e5308193ba2a72d91fce42baf11afbcad6e28dc Author: Ashish Vijaywargiya <[email protected]> AuthorDate: Sun Jun 7 17:46:13 2026 +0530 Adding support of Secret Manager framework support in Apache OFBiz. For various providers support we can add plugins components. --- build.gradle | 7 +++ dependencies.gradle | 1 + .../ofbiz/base/secret/FileBasedSecretProvider.java | 43 +++++++++++++++ .../apache/ofbiz/base/secret/SecretProvider.java | 49 +++++++++++++++++ .../ofbiz/base/secret/SecretProviderFactory.java | 63 ++++++++++++++++++++++ .../ofbiz/entity/config/model/EntityConfig.java | 13 ++--- .../entity/connection/DBCPConnectionFactory.java | 5 ++ 7 files changed, 175 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 09c4349187..fbc08af7d0 100644 --- a/build.gradle +++ b/build.gradle @@ -168,6 +168,13 @@ tasks.withType(JavaCompile) { // Enables Zip larger than 4 GB and more than 65535 entries tasks.withType(Zip) { zip64 = true } +// Multiple SecretProvider plugins each contribute the same META-INF/services/ filename. +// Only one provider should be enabled at a time (via ofbiz-component.xml enabled="true/false"). +// FIRST keeps the alphabetically first file during transitional states where two are temporarily enabled. +processResources { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + // Only used for release branches def getCurrentGitBranch() { return "git branch --show-current".execute().text.trim() diff --git a/dependencies.gradle b/dependencies.gradle index 102c3f1251..adf88d33ca 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -98,6 +98,7 @@ dependencies { runtimeOnly 'org.apache.axis2:axis2-transport-http:1.8.2' runtimeOnly 'org.apache.axis2:axis2-transport-local:1.8.2' runtimeOnly 'com.h2database:h2:2.4.240' + runtimeOnly 'com.mysql:mysql-connector-j:8.4.0' runtimeOnly 'org.apache.geronimo.specs:geronimo-jaxrpc_1.1_spec:2.1' runtimeOnly 'org.apache.logging.log4j:log4j-1.2-api:2.25.4' // for external jars using the old log4j1.2: routes logging to log4j 2 runtimeOnly 'org.apache.logging.log4j:log4j-jul:2.25.4' // for external jars using the java.util.logging: routes logging to log4j 2 diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/secret/FileBasedSecretProvider.java b/framework/base/src/main/java/org/apache/ofbiz/base/secret/FileBasedSecretProvider.java new file mode 100644 index 0000000000..2879830d5e --- /dev/null +++ b/framework/base/src/main/java/org/apache/ofbiz/base/secret/FileBasedSecretProvider.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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.ofbiz.base.secret; + +import org.apache.ofbiz.base.util.GeneralException; +import org.apache.ofbiz.base.util.UtilProperties; +import org.apache.ofbiz.base.util.UtilValidate; + +/** + * Default {@link SecretProvider} implementation that resolves secrets from + * {@code framework/base/config/passwords.properties}. + * + * <p>This preserves full backward compatibility with the existing + * {@code jdbc-password-lookup} mechanism in {@code entityengine.xml}. + * No configuration is required to use this implementation.</p> + */ +public final class FileBasedSecretProvider implements SecretProvider { + + @Override + public String getSecret(String key) throws GeneralException { + String value = UtilProperties.getPropertyValue("passwords", key); + if (UtilValidate.isEmpty(value)) { + throw new GeneralException("Secret key '" + key + "' not found in passwords.properties"); + } + return value; + } +} diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProvider.java b/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProvider.java new file mode 100644 index 0000000000..11b1f86851 --- /dev/null +++ b/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProvider.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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.ofbiz.base.secret; + +import org.apache.ofbiz.base.util.GeneralException; + +/** + * SPI for resolving secrets and credentials (e.g. database passwords, API keys). + * + * <p>Implementations must be thread-safe. Register a custom implementation via + * Java's {@link java.util.ServiceLoader} by providing a file named + * {@code META-INF/services/org.apache.ofbiz.base.secret.SecretProvider} in + * your plugin JAR containing the fully-qualified class name of your + * implementation. If no custom implementation is registered, + * {@link FileBasedSecretProvider} is used as the default, which resolves + * secrets from {@code framework/base/config/passwords.properties}.</p> + * + * <p>Example entry in a vault plugin's service descriptor:</p> + * <pre> + * org.example.ofbiz.vault.AwsSecretsManagerProvider + * </pre> + */ +public interface SecretProvider { + + /** + * Returns the secret value for the given key. + * + * @param key the identifier for the secret (e.g. {@code "jdbc-password.mydb"}) + * @return the resolved secret value, never {@code null} or empty + * @throws GeneralException if the secret cannot be found or an error occurs during resolution + */ + String getSecret(String key) throws GeneralException; +} diff --git a/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProviderFactory.java b/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProviderFactory.java new file mode 100644 index 0000000000..9d8ec088ac --- /dev/null +++ b/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProviderFactory.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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.ofbiz.base.secret; + +import java.util.Iterator; +import java.util.ServiceLoader; + +import org.apache.ofbiz.base.lang.ThreadSafe; +import org.apache.ofbiz.base.util.Debug; + +/** + * Factory that provides the active {@link SecretProvider} instance. + * + * <p>On first access the factory uses Java's {@link ServiceLoader} to discover + * a custom {@link SecretProvider} registered in any plugin's + * {@code META-INF/services/org.apache.ofbiz.base.secret.SecretProvider} file. + * If none is found, {@link FileBasedSecretProvider} is used automatically, + * preserving backward compatibility with {@code passwords.properties}.</p> + * + * <p>This mirrors the pattern already used by + * {@link org.apache.ofbiz.security.SecurityFactory}.</p> + */ +@ThreadSafe +public final class SecretProviderFactory { + + private static final String MODULE = SecretProviderFactory.class.getName(); + + private static final SecretProvider INSTANCE = loadProvider(); + + private static SecretProvider loadProvider() { + Iterator<SecretProvider> it = ServiceLoader.load(SecretProvider.class).iterator(); + if (it.hasNext()) { + SecretProvider provider = it.next(); + Debug.logInfo("SecretProvider: using custom implementation " + provider.getClass().getName(), MODULE); + return provider; + } + Debug.logInfo("SecretProvider: no custom implementation found, using FileBasedSecretProvider", MODULE); + return new FileBasedSecretProvider(); + } + + /** Returns the active {@link SecretProvider} instance. */ + public static SecretProvider getInstance() { + return INSTANCE; + } + + private SecretProviderFactory() { } +} diff --git a/framework/entity/src/main/java/org/apache/ofbiz/entity/config/model/EntityConfig.java b/framework/entity/src/main/java/org/apache/ofbiz/entity/config/model/EntityConfig.java index 1861e176cf..ff65892e7a 100644 --- a/framework/entity/src/main/java/org/apache/ofbiz/entity/config/model/EntityConfig.java +++ b/framework/entity/src/main/java/org/apache/ofbiz/entity/config/model/EntityConfig.java @@ -26,8 +26,9 @@ import java.util.List; import java.util.Map; import org.apache.ofbiz.base.lang.ThreadSafe; +import org.apache.ofbiz.base.secret.SecretProviderFactory; import org.apache.ofbiz.base.util.Debug; -import org.apache.ofbiz.base.util.UtilProperties; +import org.apache.ofbiz.base.util.GeneralException; import org.apache.ofbiz.base.util.UtilURL; import org.apache.ofbiz.base.util.UtilXml; import org.apache.ofbiz.entity.GenericEntityConfException; @@ -353,12 +354,12 @@ public final class EntityConfig { + inlineJdbcElement.getLineNumber()); } String key = "jdbc-password.".concat(jdbcPasswordLookup); - jdbcPassword = UtilProperties.getPropertyValue("passwords", key); - if (jdbcPassword.isEmpty()) { - throw new GenericEntityConfException("'" + key + "' property not found in passwords.properties file for inline-jdbc element, line: " - + inlineJdbcElement.getLineNumber()); + try { + return SecretProviderFactory.getInstance().getSecret(key); + } catch (GeneralException e) { + throw new GenericEntityConfException("Secret not found for key '" + key + "' for inline-jdbc element, line: " + + inlineJdbcElement.getLineNumber() + " - " + e.getMessage()); } - return jdbcPassword; } /** Returns the <code><datasource></code> child elements as a <code>Map</code>. */ diff --git a/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java b/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java index 45ffe2ef11..98e2759d5b 100644 --- a/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java +++ b/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java @@ -152,6 +152,11 @@ public class DBCPConnectionFactory implements ConnectionFactory { GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<>(factory, poolConfig); factory.setPool(pool); + try { + pool.preparePool(); + } catch (Exception e) { + Debug.logWarning("Could not pre-warm connection pool: " + e.getMessage(), MODULE); + } mds = new DebugManagedDataSource<>(pool, xacf.getTransactionRegistry()); mds.setAccessToUnderlyingConnectionAllowed(true);

