You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2026/09/03 13:43:02 UTC
(knox) branch master updated: KNOX-3426: Embedded-Derby delegation policy service (#1374)
This is an automated email from the ASF dual-hosted git repository.
smolnar82 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new af3b2989a KNOX-3426: Embedded-Derby delegation policy service (#1374)
af3b2989a is described below
commit af3b2989a142f9196f81308a3779df6531fc40ca
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Thu Sep 3 15:42:56 2026 +0200
KNOX-3426: Embedded-Derby delegation policy service (#1374)
---
.../gateway/database/DerbyDatabaseCredentials.java | 49 +++++++
.../EmbeddedDerbyDatabase.java} | 83 +++++------
.../apache/knox/gateway/database/JDBCUtils.java | 11 +-
.../apache/knox/gateway/database/KnoxDatabase.java | 60 ++++++++
.../factory/DelegationPolicyServiceFactory.java | 46 +++++-
.../delegation/DelegationPolicyDatabase.java | 40 +-----
.../delegation/DerbyDBDelegationPolicyService.java | 67 +++++++++
.../delegation/JdbcDelegationPolicyService.java | 4 +
.../DerbyDBFederatedIdentityService.java | 48 +------
.../DerbyDBTrustedOidcIssuerService.java | 48 +------
.../token/impl/DerbyDBTokenStateService.java | 58 +-------
.../knox/gateway/database/KnoxDatabaseTest.java | 158 +++++++++++++++++++++
.../DelegationPolicyServiceFactoryTest.java | 98 +++++++------
.../services/factory/ServiceFactoryTest.java | 9 +-
14 files changed, 506 insertions(+), 273 deletions(-)
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/database/DerbyDatabaseCredentials.java b/gateway-server/src/main/java/org/apache/knox/gateway/database/DerbyDatabaseCredentials.java
new file mode 100644
index 000000000..060df9d88
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/database/DerbyDatabaseCredentials.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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.database;
+
+import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_PASSWORD_ALIAS_NAME;
+import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME;
+
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.MasterService;
+
+/**
+ * Shared resolution of the embedded-Derby connection credentials used by the self-provisioning
+ * {@code DerbyDB*} services (token state, federated identity, trusted OIDC issuer, delegation
+ * policy). All of these back onto the single embedded Derby database and therefore resolve the
+ * same connection user and password: read from the gateway alias store when present, otherwise
+ * falling back to a well-known default user and the master secret.
+ */
+public final class DerbyDatabaseCredentials {
+
+ /** Default connection user for the embedded Derby database when no alias is configured. */
+ public static final String DEFAULT_DB_USER_NAME = "knox";
+
+ private DerbyDatabaseCredentials() {
+ }
+
+ public static String getDatabaseUserName(AliasService aliasService) throws Exception {
+ final char[] dbUserAliasValue = aliasService.getPasswordFromAliasForGateway(DATABASE_USER_ALIAS_NAME);
+ return dbUserAliasValue != null ? new String(dbUserAliasValue) : DEFAULT_DB_USER_NAME;
+ }
+
+ public static String getDatabasePassword(AliasService aliasService, MasterService masterService) throws Exception {
+ final char[] dbPasswordAliasValue = aliasService.getPasswordFromAliasForGateway(DATABASE_PASSWORD_ALIAS_NAME);
+ return dbPasswordAliasValue != null ? new String(dbPasswordAliasValue) : new String(masterService.getMasterSecret());
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DerbyDBTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/database/EmbeddedDerbyDatabase.java
similarity index 51%
copy from gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DerbyDBTokenStateService.java
copy to gateway-server/src/main/java/org/apache/knox/gateway/database/EmbeddedDerbyDatabase.java
index 471542b32..aeaa686e2 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DerbyDBTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/database/EmbeddedDerbyDatabase.java
@@ -5,87 +5,77 @@
* 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
- *
+ * <p>
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ * <p>
* 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.knox.gateway.services.token.impl;
+package org.apache.knox.gateway.database;
import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.GATEWAY_DATABASE_NAME;
import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.GATEWAY_DATABASE_TYPE;
-import static org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_PASSWORD_ALIAS_NAME;
import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME;
import static org.apache.knox.gateway.database.DatabaseType.DERBY;
+import static org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.hadoop.conf.Configuration;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.MasterService;
import org.apache.knox.gateway.shell.jdbc.derby.DerbyDatabase;
import org.apache.knox.gateway.util.FileUtils;
-public class DerbyDBTokenStateService extends JDBCTokenStateService {
+/**
+ * Manages the lifecycle of the single embedded Apache Derby database that the KnoxIDF
+ * self-provisioning services share under {@code ${securityDir}/tokens}.
+ * <p>
+ * Extracted so that {@code DerbyDBTokenStateService} and the KnoxIDF {@code DerbyDB*Service}s
+ * (federated identity, trusted OIDC issuer, delegation policy) do not each duplicate the identical
+ * boot/shutdown logic. The {@code ;create=true} JDBC URL is idempotent, so several services
+ * starting their own instance simply connect to the already-booted database.
+ */
+public final class EmbeddedDerbyDatabase {
- public static final String DEFAULT_TOKEN_DB_USER_NAME = "knox";
+ /** Folder name (under the gateway security dir) of the shared embedded Derby database. */
public static final String DB_NAME = "tokens";
+ private final Path folder;
private DerbyDatabase derbyDatabase;
- private Path derbyDatabaseFolder;
- private MasterService masterService;
- public void setMasterService(MasterService masterService) {
- this.masterService = masterService;
+ public EmbeddedDerbyDatabase(GatewayConfig config) {
+ this.folder = Paths.get(config.getGatewaySecurityDir(), DB_NAME);
}
- @Override
- public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
- try {
- derbyDatabaseFolder = Paths.get(config.getGatewaySecurityDir(), DB_NAME);
- startDerby();
- ((Configuration) config).set(GATEWAY_DATABASE_TYPE, DERBY.type());
- ((Configuration) config).set(GATEWAY_DATABASE_NAME, derbyDatabaseFolder.toString());
- getAliasService().addAliasForCluster(NO_CLUSTER_NAME, DATABASE_USER_ALIAS_NAME, getDatabaseUserName());
- getAliasService().addAliasForCluster(NO_CLUSTER_NAME, DATABASE_PASSWORD_ALIAS_NAME, getDatabasePassword());
- super.init(config, options);
+ /**
+ * Boots the embedded Derby database and points the shared {@link GatewayConfig} at it: starts the
+ * network server, sets the database type/name, ensures the connection user/password aliases exist
+ * and tightens the folder permissions. Callers delegate the actual persistence to their JDBC
+ * service by invoking {@code super.init(...)} afterwards.
+ */
+ public void start(GatewayConfig config, AliasService aliasService, MasterService masterService) throws Exception {
+ bootDerby();
+ ((Configuration) config).set(GATEWAY_DATABASE_TYPE, DERBY.type());
+ ((Configuration) config).set(GATEWAY_DATABASE_NAME, folder.toString());
+ aliasService.addAliasForCluster(NO_CLUSTER_NAME, DATABASE_USER_ALIAS_NAME, DerbyDatabaseCredentials.getDatabaseUserName(aliasService));
+ aliasService.addAliasForCluster(NO_CLUSTER_NAME, DATABASE_PASSWORD_ALIAS_NAME, DerbyDatabaseCredentials.getDatabasePassword(aliasService, masterService));
- // we need the "x" permission too to be able to browse that folder (600 is not enough)
- if (Files.exists(derbyDatabaseFolder)) {
- FileUtils.chmod("700", derbyDatabaseFolder.toFile());
- }
- } catch (Exception e) {
- throw new ServiceLifecycleException("Error while initiating DerbyDBTokenStateService: " + e, e);
+ // we need the "x" permission too to be able to browse that folder (600 is not enough)
+ if (Files.exists(folder)) {
+ FileUtils.chmod("700", folder.toFile());
}
}
- private void startDerby() throws Exception {
- derbyDatabase = new DerbyDatabase(derbyDatabaseFolder.toString());
- derbyDatabase.create();
- TimeUnit.SECONDS.sleep(1); // give a bit of time for the server to start
- }
-
- private String getDatabasePassword() throws Exception {
- final char[] dbPasswordAliasValue = getAliasService().getPasswordFromAliasForGateway(DATABASE_PASSWORD_ALIAS_NAME);
- return dbPasswordAliasValue != null ? new String(dbPasswordAliasValue) : new String(masterService.getMasterSecret());
- }
-
- private String getDatabaseUserName() throws Exception {
- final char[] dbUserAliasValue = getAliasService().getPasswordFromAliasForGateway(DATABASE_USER_ALIAS_NAME);
- return dbUserAliasValue != null ? new String(dbUserAliasValue) : DEFAULT_TOKEN_DB_USER_NAME;
- }
-
- @Override
public void stop() throws ServiceLifecycleException {
try {
if (derbyDatabase != null) {
@@ -96,4 +86,9 @@ public class DerbyDBTokenStateService extends JDBCTokenStateService {
}
}
+ private void bootDerby() throws Exception {
+ derbyDatabase = new DerbyDatabase(folder.toString());
+ derbyDatabase.create();
+ TimeUnit.SECONDS.sleep(1); // give a bit of time for the server to start
+ }
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/database/JDBCUtils.java b/gateway-server/src/main/java/org/apache/knox/gateway/database/JDBCUtils.java
index 30e2ab59f..39b9836cc 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/database/JDBCUtils.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/database/JDBCUtils.java
@@ -63,9 +63,14 @@ public class JDBCUtils {
}
public static void createTableFromSQL(String createSqlFileName, DataSource dataSource, ClassLoader classLoader) throws Exception {
- try (InputStream is = classLoader.getResourceAsStream(createSqlFileName);
- Connection connection = dataSource.getConnection();Statement createTableStatement = connection.createStatement()) {
- String createTableSql = IOUtils.toString(is, UTF_8);
+ try (InputStream is = classLoader.getResourceAsStream(createSqlFileName)) {
+ createTableFromSQL(IOUtils.toString(is, UTF_8), dataSource);
+ }
+ }
+
+ public static void createTableFromSQL(String createTableSql, DataSource dataSource) throws Exception {
+ try (Connection connection = dataSource.getConnection();
+ Statement createTableStatement = connection.createStatement()) {
createTableStatement.execute(createTableSql);
}
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/database/KnoxDatabase.java b/gateway-server/src/main/java/org/apache/knox/gateway/database/KnoxDatabase.java
index dd6ee68e8..f5a92a8b8 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/database/KnoxDatabase.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/database/KnoxDatabase.java
@@ -16,10 +16,26 @@
*/
package org.apache.knox.gateway.database;
+import org.apache.commons.io.IOUtils;
+
import javax.sql.DataSource;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
public class KnoxDatabase {
+ // Matches the leading "CREATE TABLE [IF NOT EXISTS] <name>" of a DDL statement so the table name
+ // can be handed to the existence check. Case-insensitive; tolerates the IF NOT EXISTS that the
+ // standard (PostgreSQL/MySQL/HSQL) scripts use but Derby and Oracle omit.
+ private static final Pattern CREATE_TABLE_PATTERN =
+ Pattern.compile("(?i)^\\s*CREATE\\s+TABLE\\s+(?:IF\\s+NOT\\s+EXISTS\\s+)?([\\w.]+)");
+
protected final DataSource dataSource;
public KnoxDatabase(DataSource dataSource) {
@@ -34,4 +50,48 @@ public class KnoxDatabase {
JDBCUtils.createTableFromSQL(createSqlFileName, dataSource, getClass().getClassLoader());
}
}
+
+ protected void createTablesIfNotExist(String sqlFileName) throws Exception {
+ final Map<String, String> createSqlByTableName = parseCreateTableStatements(sqlFileName);
+ for (Map.Entry<String, String> entry : createSqlByTableName.entrySet()) {
+ if (!JDBCUtils.tableExists(entry.getKey(), dataSource)) {
+ JDBCUtils.createTableFromSQL(entry.getValue(), dataSource);
+ }
+ }
+ }
+
+ protected Map<String, String> parseCreateTableStatements(String sqlFileName) throws IOException {
+ try (InputStream is = getClass().getClassLoader().getResourceAsStream(sqlFileName)) {
+ if (is == null) {
+ throw new IllegalStateException("DDL script not found on classpath: " + sqlFileName);
+ }
+ final String script = IOUtils.toString(is, UTF_8);
+ final String withoutLicenseHeader = removeLicenseHeader(script);
+ final Map<String, String> createSqlByTableName = new LinkedHashMap<>();
+ for (String statement : withoutLicenseHeader.split(";")) {
+ final String trimmed = statement.trim();
+ if (!trimmed.isEmpty()) {
+ createSqlByTableName.put(extractTableName(trimmed), trimmed);
+ }
+ }
+ return createSqlByTableName;
+ }
+ }
+
+ /* The DDL scripts begin with the ASF license as a block of "--" comment lines. Stripping only
+ the comment lines (rather than cutting everything before the first CREATE TABLE) removes the
+ header while leaving every SQL statement intact -- notably the PostgreSQL pg_advisory_lock
+ statements that guard concurrent CREATE TABLE IF NOT EXISTS, which the earlier approach cut.
+ */
+ private String removeLicenseHeader(String script) {
+ return script.replaceAll("(?m)^\\s*--.*$", "");
+ }
+
+ private String extractTableName(String createTableStatement) {
+ final Matcher matcher = CREATE_TABLE_PATTERN.matcher(createTableStatement);
+ if (!matcher.find()) {
+ throw new IllegalStateException("Expected a CREATE TABLE statement but found: " + createTableStatement);
+ }
+ return matcher.group(1);
+ }
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactory.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactory.java
index 01f0b2bcc..065ab0684 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactory.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactory.java
@@ -18,11 +18,13 @@ package org.apache.knox.gateway.services.factory;
import org.apache.knox.gateway.GatewayMessages;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.database.DatabaseType;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.Service;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.ServiceType;
+import org.apache.knox.gateway.services.knoxidf.delegation.DerbyDBDelegationPolicyService;
import org.apache.knox.gateway.services.knoxidf.delegation.EmptyDelegationPolicyService;
import org.apache.knox.gateway.services.knoxidf.delegation.JdbcDelegationPolicyService;
@@ -41,14 +43,18 @@ public class DelegationPolicyServiceFactory extends AbstractServiceFactory {
throws ServiceLifecycleException {
String implementationToUse = implementation;
+ // No explicit impl configured: auto-select a persistence backend when KnoxIDF is deployed.
+ // Otherwise honor the configured impl (very likely a prod JDBC store).
if (isEmptyDefaultImplementation(implementationToUse) && isKnoxIdfEnabledInAnyTopology(gatewayServices, gatewayConfig)) {
- implementationToUse = JdbcDelegationPolicyService.class.getName();
+ implementationToUse = chooseAutoImplementation(gatewayConfig);
}
Service service = null;
if (shouldCreateService(implementationToUse)) {
if (matchesImplementation(implementationToUse, EmptyDelegationPolicyService.class, true)) {
service = new EmptyDelegationPolicyService();
+ } else if (matchesImplementation(implementationToUse, DerbyDBDelegationPolicyService.class)) {
+ service = createDerbyService(gatewayServices, gatewayConfig, options);
} else if (matchesImplementation(implementationToUse, JdbcDelegationPolicyService.class)) {
service = createJdbcService(gatewayServices, gatewayConfig, options);
}
@@ -59,6 +65,42 @@ public class DelegationPolicyServiceFactory extends AbstractServiceFactory {
return service;
}
+ /**
+ * Chooses the auto-enabled implementation when KnoxIDF is deployed with no explicit impl: an
+ * operator-configured external database wins (very likely a prod JDBC store), otherwise a
+ * self-provisioning embedded Derby store (the {@code none}/{@code derbydb} default) so the
+ * delegation policy registry works out of the box without any extra infrastructure.
+ */
+ String chooseAutoImplementation(GatewayConfig gatewayConfig) {
+ return isExternalDatabaseConfigured(gatewayConfig)
+ ? JdbcDelegationPolicyService.class.getName()
+ : DerbyDBDelegationPolicyService.class.getName();
+ }
+
+ private boolean isExternalDatabaseConfigured(GatewayConfig gatewayConfig) {
+ final String databaseType = gatewayConfig.getDatabaseType();
+ try {
+ return DatabaseType.fromString(databaseType) != DatabaseType.DERBY;
+ } catch (IllegalArgumentException e) {
+ // "none" (the default) or any unrecognized value: no real external DB -> use Derby.
+ return false;
+ }
+ }
+
+ private Service createDerbyService(GatewayServices gatewayServices, GatewayConfig gatewayConfig, Map<String, String> options)
+ throws ServiceLifecycleException {
+ try {
+ final DerbyDBDelegationPolicyService derbyService = new DerbyDBDelegationPolicyService();
+ derbyService.setAliasService(getAliasService(gatewayServices));
+ derbyService.setMasterService(getMasterService(gatewayServices));
+ derbyService.init(gatewayConfig, options);
+ return derbyService;
+ } catch (ServiceLifecycleException e) {
+ LOG.errorInitializingService(DerbyDBDelegationPolicyService.class.getName(), e.getMessage(), e);
+ return new EmptyDelegationPolicyService();
+ }
+ }
+
private Service createJdbcService(GatewayServices gatewayServices, GatewayConfig gatewayConfig, Map<String, String> options)
throws ServiceLifecycleException {
try {
@@ -79,6 +121,6 @@ public class DelegationPolicyServiceFactory extends AbstractServiceFactory {
@Override
protected Collection<String> getKnownImplementations() {
- return List.of(DEFAULT_IMPLEMENTATION, JdbcDelegationPolicyService.class.getName());
+ return List.of(DEFAULT_IMPLEMENTATION, JdbcDelegationPolicyService.class.getName(), DerbyDBDelegationPolicyService.class.getName());
}
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyDatabase.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyDatabase.java
index 6631feef5..516dedb95 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyDatabase.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DelegationPolicyDatabase.java
@@ -16,13 +16,10 @@
*/
package org.apache.knox.gateway.services.knoxidf.delegation;
-import org.apache.commons.io.IOUtils;
import org.apache.knox.gateway.database.DatabaseType;
-import org.apache.knox.gateway.database.JDBCUtils;
import org.apache.knox.gateway.database.KnoxDatabase;
import javax.sql.DataSource;
-import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
@@ -37,8 +34,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.UUID;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
/**
* JDBC helper for the five DELEGATION_POLICIES tables.
* All SQL uses {@link PreparedStatement} with {@code ?} parameters only.
@@ -128,40 +123,7 @@ class DelegationPolicyDatabase extends KnoxDatabase {
this.selectAllSql = SELECT_ALL_BASE_SQL + " FETCH FIRST " + (listMaxTotal + 1) + " ROWS ONLY";
this.selectAllFilteredSql = SELECT_ALL_BASE_SQL + " WHERE actor_authority = ? FETCH FIRST " + (listMaxPerAuthority + 1) + " ROWS ONLY";
final DatabaseType databaseType = DatabaseType.fromString(dbType);
- createDelegationTablesIfNotExists(databaseType.delegationPolicyTablesSql());
- }
-
- /**
- * Multi-statement DDL runner: checks if DELEGATION_POLICIES exists, then strips SQL line
- * comments, splits on {@code ;}, and executes each non-empty statement individually.
- * {@link JDBCUtils#createTableFromSQL} handles only single statements; delegation needs five.
- * Comment stripping must happen before the split because the ASF license header contains a
- * semicolon inside a {@code --} comment line, which would otherwise produce a spurious token.
- */
- private void createDelegationTablesIfNotExists(String sqlFileName) throws Exception {
- if (!JDBCUtils.tableExists(CORE_TABLE, dataSource)) {
- try (InputStream is = getClass().getClassLoader().getResourceAsStream(sqlFileName);
- Connection connection = dataSource.getConnection()) {
- if (is == null) {
- throw new IllegalStateException("DDL script not found on classpath: " + sqlFileName);
- }
- final String script = IOUtils.toString(is, UTF_8);
- final StringBuilder stripped = new StringBuilder();
- for (String line : script.split("\n")) {
- if (!line.trim().startsWith("--")) {
- stripped.append(line).append('\n');
- }
- }
- for (String statement : stripped.toString().split(";")) {
- final String trimmed = statement.trim();
- if (!trimmed.isEmpty()) {
- try (java.sql.Statement stmt = connection.createStatement()) {
- stmt.execute(trimmed);
- }
- }
- }
- }
- }
+ createTablesIfNotExist(databaseType.delegationPolicyTablesSql());
}
String insertPolicy(DelegationPolicy policy) throws SQLException {
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DerbyDBDelegationPolicyService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DerbyDBDelegationPolicyService.java
new file mode 100644
index 000000000..0446bc174
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/DerbyDBDelegationPolicyService.java
@@ -0,0 +1,67 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.services.knoxidf.delegation;
+
+import java.util.Map;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.database.EmbeddedDerbyDatabase;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.apache.knox.gateway.services.security.MasterService;
+import org.apache.knox.gateway.services.token.impl.DerbyDBTokenStateService;
+
+/**
+ * A self-provisioning, embedded-Derby backed {@link DelegationPolicyService}. This is the
+ * auto-enabled default when KnoxIDF is deployed without an operator-configured external database,
+ * mirroring how {@link DerbyDBTokenStateService} is the default token-state service and
+ * {@code DerbyDBFederatedIdentityService}/{@code DerbyDBTrustedOidcIssuerService} are the default
+ * federated-identity and trusted-OIDC-issuer services.
+ * <p>
+ * It reuses the single embedded Derby database that the token-state service already provisions
+ * under {@code ${securityDir}/tokens} (the {@code ;create=true} JDBC URL is idempotent, so
+ * connecting to an already-booted database simply connects), sets the shared {@link GatewayConfig}
+ * to point at it, ensures the connection user/password aliases exist, and then delegates all
+ * persistence to {@link JdbcDelegationPolicyService} (which builds the
+ * {@link DelegationPolicyDatabase} and self-creates its tables).
+ */
+public class DerbyDBDelegationPolicyService extends JdbcDelegationPolicyService {
+
+ private EmbeddedDerbyDatabase embeddedDerbyDatabase;
+ private MasterService masterService;
+
+ public void setMasterService(MasterService masterService) {
+ this.masterService = masterService;
+ }
+
+ @Override
+ public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
+ try {
+ embeddedDerbyDatabase = new EmbeddedDerbyDatabase(config);
+ embeddedDerbyDatabase.start(config, getAliasService(), masterService);
+ super.init(config, options);
+ } catch (Exception e) {
+ throw new ServiceLifecycleException("Error while initiating DerbyDBDelegationPolicyService: " + e, e);
+ }
+ }
+
+ @Override
+ public void stop() throws ServiceLifecycleException {
+ if (embeddedDerbyDatabase != null) {
+ embeddedDerbyDatabase.stop();
+ }
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/JdbcDelegationPolicyService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/JdbcDelegationPolicyService.java
index fe1def125..8bb74765b 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/JdbcDelegationPolicyService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/delegation/JdbcDelegationPolicyService.java
@@ -87,6 +87,10 @@ public class JdbcDelegationPolicyService implements DelegationPolicyService {
this.aliasService = aliasService;
}
+ protected AliasService getAliasService() {
+ return aliasService;
+ }
+
@Override
public DelegationPolicy register(DelegationPolicy policy) {
try {
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/DerbyDBFederatedIdentityService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/DerbyDBFederatedIdentityService.java
index aa4bec22c..475b285e7 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/DerbyDBFederatedIdentityService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/federation/DerbyDBFederatedIdentityService.java
@@ -16,24 +16,13 @@
*/
package org.apache.knox.gateway.services.knoxidf.federation;
-import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.GATEWAY_DATABASE_NAME;
-import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.GATEWAY_DATABASE_TYPE;
-import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_PASSWORD_ALIAS_NAME;
-import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME;
-import static org.apache.knox.gateway.database.DatabaseType.DERBY;
-import static org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import org.apache.hadoop.conf.Configuration;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.database.EmbeddedDerbyDatabase;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.MasterService;
import org.apache.knox.gateway.services.token.impl.DerbyDBTokenStateService;
-import org.apache.knox.gateway.shell.jdbc.derby.DerbyDatabase;
/**
* A self-provisioning, embedded-Derby backed {@link FederatedIdentityService}. This is the
@@ -49,8 +38,7 @@ import org.apache.knox.gateway.shell.jdbc.derby.DerbyDatabase;
*/
public class DerbyDBFederatedIdentityService extends JdbcFederatedIdentityService {
- private DerbyDatabase derbyDatabase;
- private Path derbyDatabaseFolder;
+ private EmbeddedDerbyDatabase embeddedDerbyDatabase;
private MasterService masterService;
public void setMasterService(MasterService masterService) {
@@ -60,42 +48,18 @@ public class DerbyDBFederatedIdentityService extends JdbcFederatedIdentityServic
@Override
public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
try {
- derbyDatabaseFolder = Paths.get(config.getGatewaySecurityDir(), DerbyDBTokenStateService.DB_NAME);
- startDerby();
- ((Configuration) config).set(GATEWAY_DATABASE_TYPE, DERBY.type());
- ((Configuration) config).set(GATEWAY_DATABASE_NAME, derbyDatabaseFolder.toString());
- getAliasService().addAliasForCluster(NO_CLUSTER_NAME, DATABASE_USER_ALIAS_NAME, getDatabaseUserName());
- getAliasService().addAliasForCluster(NO_CLUSTER_NAME, DATABASE_PASSWORD_ALIAS_NAME, getDatabasePassword());
+ embeddedDerbyDatabase = new EmbeddedDerbyDatabase(config);
+ embeddedDerbyDatabase.start(config, getAliasService(), masterService);
super.init(config, options);
} catch (Exception e) {
throw new ServiceLifecycleException("Error while initiating DerbyDBFederatedIdentityService: " + e, e);
}
}
- private void startDerby() throws Exception {
- derbyDatabase = new DerbyDatabase(derbyDatabaseFolder.toString());
- derbyDatabase.create();
- TimeUnit.SECONDS.sleep(1); // give a bit of time for the server to start
- }
-
- private String getDatabasePassword() throws Exception {
- final char[] dbPasswordAliasValue = getAliasService().getPasswordFromAliasForGateway(DATABASE_PASSWORD_ALIAS_NAME);
- return dbPasswordAliasValue != null ? new String(dbPasswordAliasValue) : new String(masterService.getMasterSecret());
- }
-
- private String getDatabaseUserName() throws Exception {
- final char[] dbUserAliasValue = getAliasService().getPasswordFromAliasForGateway(DATABASE_USER_ALIAS_NAME);
- return dbUserAliasValue != null ? new String(dbUserAliasValue) : DerbyDBTokenStateService.DEFAULT_TOKEN_DB_USER_NAME;
- }
-
@Override
public void stop() throws ServiceLifecycleException {
- try {
- if (derbyDatabase != null) {
- derbyDatabase.shutdown();
- }
- } catch (Exception e) {
- throw new ServiceLifecycleException("Error while shutting down Derby Database", e);
+ if (embeddedDerbyDatabase != null) {
+ embeddedDerbyDatabase.stop();
}
}
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/DerbyDBTrustedOidcIssuerService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/DerbyDBTrustedOidcIssuerService.java
index f1c73e9b3..3f0bbfc2e 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/DerbyDBTrustedOidcIssuerService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/knoxidf/trustedoidcissuer/DerbyDBTrustedOidcIssuerService.java
@@ -16,24 +16,13 @@
*/
package org.apache.knox.gateway.services.knoxidf.trustedoidcissuer;
-import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.GATEWAY_DATABASE_NAME;
-import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.GATEWAY_DATABASE_TYPE;
-import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_PASSWORD_ALIAS_NAME;
-import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME;
-import static org.apache.knox.gateway.database.DatabaseType.DERBY;
-import static org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import org.apache.hadoop.conf.Configuration;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.database.EmbeddedDerbyDatabase;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.MasterService;
import org.apache.knox.gateway.services.token.impl.DerbyDBTokenStateService;
-import org.apache.knox.gateway.shell.jdbc.derby.DerbyDatabase;
/**
* A self-provisioning, embedded-Derby backed {@link TrustedOidcIssuerService}. This is the
@@ -50,8 +39,7 @@ import org.apache.knox.gateway.shell.jdbc.derby.DerbyDatabase;
*/
public class DerbyDBTrustedOidcIssuerService extends JdbcTrustedOidcIssuerService {
- private DerbyDatabase derbyDatabase;
- private Path derbyDatabaseFolder;
+ private EmbeddedDerbyDatabase embeddedDerbyDatabase;
private MasterService masterService;
public void setMasterService(MasterService masterService) {
@@ -61,42 +49,18 @@ public class DerbyDBTrustedOidcIssuerService extends JdbcTrustedOidcIssuerServic
@Override
public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
try {
- derbyDatabaseFolder = Paths.get(config.getGatewaySecurityDir(), DerbyDBTokenStateService.DB_NAME);
- startDerby();
- ((Configuration) config).set(GATEWAY_DATABASE_TYPE, DERBY.type());
- ((Configuration) config).set(GATEWAY_DATABASE_NAME, derbyDatabaseFolder.toString());
- getAliasService().addAliasForCluster(NO_CLUSTER_NAME, DATABASE_USER_ALIAS_NAME, getDatabaseUserName());
- getAliasService().addAliasForCluster(NO_CLUSTER_NAME, DATABASE_PASSWORD_ALIAS_NAME, getDatabasePassword());
+ embeddedDerbyDatabase = new EmbeddedDerbyDatabase(config);
+ embeddedDerbyDatabase.start(config, getAliasService(), masterService);
super.init(config, options);
} catch (Exception e) {
throw new ServiceLifecycleException("Error while initiating DerbyDBTrustedOidcIssuerService: " + e, e);
}
}
- private void startDerby() throws Exception {
- derbyDatabase = new DerbyDatabase(derbyDatabaseFolder.toString());
- derbyDatabase.create();
- TimeUnit.SECONDS.sleep(1); // give a bit of time for the server to start
- }
-
- private String getDatabasePassword() throws Exception {
- final char[] dbPasswordAliasValue = getAliasService().getPasswordFromAliasForGateway(DATABASE_PASSWORD_ALIAS_NAME);
- return dbPasswordAliasValue != null ? new String(dbPasswordAliasValue) : new String(masterService.getMasterSecret());
- }
-
- private String getDatabaseUserName() throws Exception {
- final char[] dbUserAliasValue = getAliasService().getPasswordFromAliasForGateway(DATABASE_USER_ALIAS_NAME);
- return dbUserAliasValue != null ? new String(dbUserAliasValue) : DerbyDBTokenStateService.DEFAULT_TOKEN_DB_USER_NAME;
- }
-
@Override
public void stop() throws ServiceLifecycleException {
- try {
- if (derbyDatabase != null) {
- derbyDatabase.shutdown();
- }
- } catch (Exception e) {
- throw new ServiceLifecycleException("Error while shutting down Derby Database", e);
+ if (embeddedDerbyDatabase != null) {
+ embeddedDerbyDatabase.stop();
}
}
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DerbyDBTokenStateService.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DerbyDBTokenStateService.java
index 471542b32..5d281856a 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DerbyDBTokenStateService.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DerbyDBTokenStateService.java
@@ -16,33 +16,16 @@
*/
package org.apache.knox.gateway.services.token.impl;
-import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.GATEWAY_DATABASE_NAME;
-import static org.apache.knox.gateway.config.impl.GatewayConfigImpl.GATEWAY_DATABASE_TYPE;
-import static org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
-import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_PASSWORD_ALIAS_NAME;
-import static org.apache.knox.gateway.database.AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME;
-import static org.apache.knox.gateway.database.DatabaseType.DERBY;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import org.apache.hadoop.conf.Configuration;
import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.database.EmbeddedDerbyDatabase;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.MasterService;
-import org.apache.knox.gateway.shell.jdbc.derby.DerbyDatabase;
-import org.apache.knox.gateway.util.FileUtils;
public class DerbyDBTokenStateService extends JDBCTokenStateService {
- public static final String DEFAULT_TOKEN_DB_USER_NAME = "knox";
- public static final String DB_NAME = "tokens";
-
- private DerbyDatabase derbyDatabase;
- private Path derbyDatabaseFolder;
+ private EmbeddedDerbyDatabase embeddedDerbyDatabase;
private MasterService masterService;
public void setMasterService(MasterService masterService) {
@@ -52,47 +35,18 @@ public class DerbyDBTokenStateService extends JDBCTokenStateService {
@Override
public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
try {
- derbyDatabaseFolder = Paths.get(config.getGatewaySecurityDir(), DB_NAME);
- startDerby();
- ((Configuration) config).set(GATEWAY_DATABASE_TYPE, DERBY.type());
- ((Configuration) config).set(GATEWAY_DATABASE_NAME, derbyDatabaseFolder.toString());
- getAliasService().addAliasForCluster(NO_CLUSTER_NAME, DATABASE_USER_ALIAS_NAME, getDatabaseUserName());
- getAliasService().addAliasForCluster(NO_CLUSTER_NAME, DATABASE_PASSWORD_ALIAS_NAME, getDatabasePassword());
+ embeddedDerbyDatabase = new EmbeddedDerbyDatabase(config);
+ embeddedDerbyDatabase.start(config, getAliasService(), masterService);
super.init(config, options);
-
- // we need the "x" permission too to be able to browse that folder (600 is not enough)
- if (Files.exists(derbyDatabaseFolder)) {
- FileUtils.chmod("700", derbyDatabaseFolder.toFile());
- }
} catch (Exception e) {
throw new ServiceLifecycleException("Error while initiating DerbyDBTokenStateService: " + e, e);
}
}
- private void startDerby() throws Exception {
- derbyDatabase = new DerbyDatabase(derbyDatabaseFolder.toString());
- derbyDatabase.create();
- TimeUnit.SECONDS.sleep(1); // give a bit of time for the server to start
- }
-
- private String getDatabasePassword() throws Exception {
- final char[] dbPasswordAliasValue = getAliasService().getPasswordFromAliasForGateway(DATABASE_PASSWORD_ALIAS_NAME);
- return dbPasswordAliasValue != null ? new String(dbPasswordAliasValue) : new String(masterService.getMasterSecret());
- }
-
- private String getDatabaseUserName() throws Exception {
- final char[] dbUserAliasValue = getAliasService().getPasswordFromAliasForGateway(DATABASE_USER_ALIAS_NAME);
- return dbUserAliasValue != null ? new String(dbUserAliasValue) : DEFAULT_TOKEN_DB_USER_NAME;
- }
-
@Override
public void stop() throws ServiceLifecycleException {
- try {
- if (derbyDatabase != null) {
- derbyDatabase.shutdown();
- }
- } catch (Exception e) {
- throw new ServiceLifecycleException("Error while shutting down Derby Database", e);
+ if (embeddedDerbyDatabase != null) {
+ embeddedDerbyDatabase.stop();
}
}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/database/KnoxDatabaseTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/database/KnoxDatabaseTest.java
new file mode 100644
index 000000000..b5b3f8a70
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/database/KnoxDatabaseTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.database;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.hsqldb.jdbc.JDBCDataSource;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Exercises {@link KnoxDatabase#createTablesIfNotExist(String)} and its
+ * {@link KnoxDatabase#parseCreateTableStatements(String)} helper directly against an in-memory HSQL
+ * database, using the standard (PostgreSQL/HSQL) KnoxIDF delegation-policy DDL as a representative
+ * multi-statement, foreign-key-ordered script.
+ */
+public class KnoxDatabaseTest {
+
+ public static final String USER = "sa";
+ public static final String PASSWORD = "";
+
+ private static final String DDL = AbstractDataSourceFactory.KNOXIDF_DELEGATION_POLICY_TABLES_SQL;
+
+ // Parent-first: matches the order the statements appear in the DDL and the order they must be
+ // created in to satisfy the foreign keys.
+ private static final List<String> EXPECTED_TABLES = Arrays.asList(
+ "DELEGATION_POLICIES",
+ "DELEGATION_POLICY_USERS",
+ "DELEGATION_POLICY_GROUPS",
+ "DELEGATION_POLICY_RESOURCES",
+ "DELEGATION_POLICY_RESOURCE_SCOPES");
+
+ private static JDBCDataSource dataSource;
+ private KnoxDatabase db;
+
+ @BeforeClass
+ public static void setUpClass() {
+ dataSource = new JDBCDataSource();
+ dataSource.setDatabaseName("knox_database_test");
+ dataSource.setUser(USER);
+ dataSource.setPassword(PASSWORD);
+ dataSource.setUrl("jdbc:hsqldb:mem:knoxdatabasetest;sql.syntax_pgs=true"); // sql.syntax_pgs => use postgres syntax
+ }
+
+ @Before
+ public void setUp() {
+ db = new KnoxDatabase(dataSource);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Child-first so the foreign keys don't block the drops; leaves a clean schema for the next test.
+ final List<String> reversed = new ArrayList<>(EXPECTED_TABLES);
+ java.util.Collections.reverse(reversed);
+ try (Connection connection = dataSource.getConnection(USER, PASSWORD);
+ Statement statement = connection.createStatement()) {
+ for (String table : reversed) {
+ statement.execute("DROP TABLE IF EXISTS " + table);
+ }
+ }
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws Exception {
+ try (Connection connection = dataSource.getConnection(USER, PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.execute("SHUTDOWN");
+ }
+ }
+
+ @Test
+ public void shouldParseAllCreateStatementsInOrderWithoutLicenseHeader() throws Exception {
+ final Map<String, String> statements = db.parseCreateTableStatements(DDL);
+
+ assertEquals(EXPECTED_TABLES, new ArrayList<>(statements.keySet()));
+ for (Map.Entry<String, String> entry : statements.entrySet()) {
+ assertTrue("statement for " + entry.getKey() + " should start with CREATE TABLE",
+ entry.getValue().toUpperCase(java.util.Locale.ROOT).startsWith("CREATE TABLE"));
+ assertFalse("license header should have been stripped",
+ entry.getValue().contains("Licensed to the Apache"));
+ }
+ }
+
+ @Test
+ public void shouldCreateAllTables() throws Exception {
+ for (String table : EXPECTED_TABLES) {
+ assertFalse(table + " should not exist before creation", JDBCUtils.tableExists(table, dataSource));
+ }
+
+ db.createTablesIfNotExist(DDL);
+
+ for (String table : EXPECTED_TABLES) {
+ assertTrue(table + " should exist after creation", JDBCUtils.tableExists(table, dataSource));
+ }
+ }
+
+ @Test
+ public void shouldBeIdempotentWhenAllTablesAlreadyExist() throws Exception {
+ db.createTablesIfNotExist(DDL);
+ // A second run must not fail even though every table is already present.
+ db.createTablesIfNotExist(DDL);
+
+ for (String table : EXPECTED_TABLES) {
+ assertTrue(table + " should still exist", JDBCUtils.tableExists(table, dataSource));
+ }
+ }
+
+ @Test
+ public void shouldRecreateOnlyMissingTables() throws Exception {
+ db.createTablesIfNotExist(DDL);
+
+ // Drop a leaf table (nothing references it) to simulate a partially provisioned schema.
+ final String leaf = "DELEGATION_POLICY_RESOURCE_SCOPES";
+ try (Connection connection = dataSource.getConnection(USER, PASSWORD);
+ Statement statement = connection.createStatement()) {
+ statement.execute("DROP TABLE " + leaf);
+ }
+ assertFalse(JDBCUtils.tableExists(leaf, dataSource));
+
+ db.createTablesIfNotExist(DDL);
+
+ for (String table : EXPECTED_TABLES) {
+ assertTrue(table + " should exist after re-provisioning", JDBCUtils.tableExists(table, dataSource));
+ }
+ }
+
+ @Test
+ public void shouldFailWhenDdlScriptIsMissing() {
+ assertThrows(IllegalStateException.class, () -> db.parseCreateTableStatements("does-not-exist.sql"));
+ }
+}
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactoryTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactoryTest.java
index 1cec419e3..eba048a01 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactoryTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/factory/DelegationPolicyServiceFactoryTest.java
@@ -22,21 +22,23 @@ import org.apache.knox.gateway.database.DatabaseType;
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.Service;
import org.apache.knox.gateway.services.ServiceType;
+import org.apache.knox.gateway.services.knoxidf.delegation.DerbyDBDelegationPolicyService;
import org.apache.knox.gateway.services.knoxidf.delegation.EmptyDelegationPolicyService;
import org.apache.knox.gateway.services.knoxidf.delegation.JdbcDelegationPolicyService;
import org.apache.knox.gateway.services.knoxidf.delegation.PolicyCheckRequest;
import org.apache.knox.gateway.services.knoxidf.delegation.PolicyDecision;
import org.apache.knox.gateway.services.knoxidf.delegation.DelegationPolicyService;
import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.MasterService;
import org.apache.knox.gateway.services.topology.TopologyService;
import org.apache.knox.gateway.topology.Topology;
+import org.apache.knox.test.TestUtils;
import org.easymock.EasyMock;
import org.junit.After;
-import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
-import java.sql.DriverManager;
+import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
@@ -53,13 +55,6 @@ public class DelegationPolicyServiceFactoryTest {
private File tempDir;
private Service createdService;
- @BeforeClass
- public static void setUpClass() throws Exception {
- java.util.Locale.setDefault(java.util.Locale.US);
- DriverManager.getConnection("jdbc:derby:memory:DelegationPolicyServiceFactoryTest_knoxidf;create=true").close();
- DriverManager.getConnection("jdbc:derby:memory:DelegationPolicyServiceFactoryTest_knoxidf_admin;create=true").close();
- }
-
@After
public void tearDown() throws Exception {
if (createdService != null) {
@@ -103,69 +98,82 @@ public class DelegationPolicyServiceFactoryTest {
}
// ------------------------------------------------------------------
- // JDBC when KNOXIDF deployed
+ // Auto-implementation selection
// ------------------------------------------------------------------
@Test
- public void shouldSelectJdbcWhenKnoxIdfDeployed() throws Exception {
- tempDir = org.apache.knox.test.TestUtils.createTempDir(getClass().getName());
- final AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
- EasyMock.replay(aliasService);
-
- final TopologyService topologyService = EasyMock.createNiceMock(TopologyService.class);
- EasyMock.expect(topologyService.getTopologies()).andReturn(
- Collections.singletonList(topologyWithRole("KNOXIDF"))).anyTimes();
- EasyMock.replay(topologyService);
-
- final GatewayServices gws = EasyMock.createNiceMock(GatewayServices.class);
- EasyMock.expect(gws.getService(ServiceType.TOPOLOGY_SERVICE)).andReturn(topologyService).anyTimes();
- EasyMock.expect(gws.getService(ServiceType.ALIAS_SERVICE)).andReturn(aliasService).anyTimes();
- EasyMock.replay(gws);
+ public void shouldChooseDerbyWhenNoDatabaseConfigured() {
+ final GatewayConfigImpl config = EasyMock.createNiceMock(GatewayConfigImpl.class);
+ EasyMock.expect(config.getDatabaseType()).andReturn("none").anyTimes();
+ EasyMock.replay(config);
+ assertEquals(DerbyDBDelegationPolicyService.class.getName(), factory.chooseAutoImplementation(config));
+ }
+ @Test
+ public void shouldChooseDerbyWhenDatabaseTypeIsDerby() {
final GatewayConfigImpl config = EasyMock.createNiceMock(GatewayConfigImpl.class);
EasyMock.expect(config.getDatabaseType()).andReturn(DatabaseType.DERBY.type()).anyTimes();
- EasyMock.expect(config.getDatabaseName())
- .andReturn("memory:" + getClass().getSimpleName() + "_knoxidf").anyTimes();
- EasyMock.expect(config.getDelegationServiceTokenTtlSec()).andReturn(3600).anyTimes();
- EasyMock.expect(config.getDelegationServiceListMaxTotal()).andReturn(
- org.apache.knox.gateway.config.GatewayConfig.DELEGATION_SERVICE_LIST_MAX_TOTAL_DEFAULT).anyTimes();
- EasyMock.expect(config.getDelegationServiceListMaxPerAuthority()).andReturn(
- org.apache.knox.gateway.config.GatewayConfig.DELEGATION_SERVICE_LIST_MAX_PER_AUTHORITY_DEFAULT).anyTimes();
EasyMock.replay(config);
+ assertEquals(DerbyDBDelegationPolicyService.class.getName(), factory.chooseAutoImplementation(config));
+ }
- createdService = factory.create(gws, ServiceType.DELEGATION_POLICY_SERVICE, config, options, "");
+ @Test
+ public void shouldChooseJdbcWhenExternalDatabaseConfigured() {
+ final GatewayConfigImpl config = EasyMock.createNiceMock(GatewayConfigImpl.class);
+ EasyMock.expect(config.getDatabaseType()).andReturn(DatabaseType.POSTGRESQL.type()).anyTimes();
+ EasyMock.replay(config);
+ assertEquals(JdbcDelegationPolicyService.class.getName(), factory.chooseAutoImplementation(config));
+ }
+
+ // ------------------------------------------------------------------
+ // Derby auto-provisioning
+ // ------------------------------------------------------------------
+
+ @Test
+ public void shouldAutoSelectDerbyServiceWhenKnoxIdfDeployedWithoutExternalDatabase() throws Exception {
+ createdService = createAutoProvisionedDerbyService(topologyWithRole("KNOXIDF"));
assertNotNull(createdService);
- assertTrue("Expected JdbcDelegationPolicyService when KNOXIDF is deployed, got "
- + createdService.getClass().getName(), createdService instanceof JdbcDelegationPolicyService);
+ assertTrue("Expected a self-provisioning Derby-backed delegation policy service, got "
+ + createdService.getClass().getName(), createdService instanceof DerbyDBDelegationPolicyService);
}
@Test
- public void shouldSelectJdbcWhenKnoxIdfAdminDeployed() throws Exception {
- tempDir = org.apache.knox.test.TestUtils.createTempDir(getClass().getName());
+ public void shouldAutoSelectDerbyServiceWhenKnoxIdfAdminDeployedWithoutExternalDatabase() throws Exception {
+ createdService = createAutoProvisionedDerbyService(topologyWithRole("KNOXIDF_ADMIN"));
+ assertNotNull(createdService);
+ assertTrue("Expected a self-provisioning Derby-backed delegation policy service, got "
+ + createdService.getClass().getName(), createdService instanceof DerbyDBDelegationPolicyService);
+ }
+
+ private Service createAutoProvisionedDerbyService(Topology topology) throws Exception {
+ tempDir = TestUtils.createTempDir(this.getClass().getName());
+ final MasterService masterService = EasyMock.createNiceMock(MasterService.class);
+ EasyMock.expect(masterService.getMasterSecret()).andReturn("M4st3RSecret!".toCharArray()).anyTimes();
+ EasyMock.replay(masterService);
final AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
EasyMock.replay(aliasService);
final TopologyService topologyService = EasyMock.createNiceMock(TopologyService.class);
- EasyMock.expect(topologyService.getTopologies()).andReturn(
- Collections.singletonList(topologyWithRole("KNOXIDF_ADMIN"))).anyTimes();
+ EasyMock.expect(topologyService.getTopologies()).andReturn(Collections.singletonList(topology)).anyTimes();
EasyMock.replay(topologyService);
-
final GatewayServices gws = EasyMock.createNiceMock(GatewayServices.class);
EasyMock.expect(gws.getService(ServiceType.TOPOLOGY_SERVICE)).andReturn(topologyService).anyTimes();
EasyMock.expect(gws.getService(ServiceType.ALIAS_SERVICE)).andReturn(aliasService).anyTimes();
+ EasyMock.expect(gws.getService(ServiceType.MASTER_SERVICE)).andReturn(masterService).anyTimes();
EasyMock.replay(gws);
final GatewayConfigImpl config = EasyMock.createNiceMock(GatewayConfigImpl.class);
EasyMock.expect(config.getDatabaseType()).andReturn(DatabaseType.DERBY.type()).anyTimes();
- EasyMock.expect(config.getDatabaseName())
- .andReturn("memory:" + getClass().getSimpleName() + "_knoxidf_admin").anyTimes();
+ EasyMock.expect(config.getGatewaySecurityDir()).andReturn(tempDir.getAbsolutePath()).anyTimes();
+ EasyMock.expect(config.getDatabaseName()).andReturn(Paths.get(tempDir.getAbsolutePath(), "tokens").toString()).anyTimes();
EasyMock.expect(config.getDelegationServiceTokenTtlSec()).andReturn(3600).anyTimes();
+ EasyMock.expect(config.getDelegationServiceListMaxTotal()).andReturn(
+ org.apache.knox.gateway.config.GatewayConfig.DELEGATION_SERVICE_LIST_MAX_TOTAL_DEFAULT).anyTimes();
+ EasyMock.expect(config.getDelegationServiceListMaxPerAuthority()).andReturn(
+ org.apache.knox.gateway.config.GatewayConfig.DELEGATION_SERVICE_LIST_MAX_PER_AUTHORITY_DEFAULT).anyTimes();
EasyMock.replay(config);
- createdService = factory.create(gws, ServiceType.DELEGATION_POLICY_SERVICE, config, options, "");
- assertNotNull(createdService);
- assertTrue("Expected JdbcDelegationPolicyService when KNOXIDF_ADMIN is deployed, got "
- + createdService.getClass().getName(), createdService instanceof JdbcDelegationPolicyService);
+ return factory.create(gws, ServiceType.DELEGATION_POLICY_SERVICE, config, options, "");
}
// ------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/factory/ServiceFactoryTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/factory/ServiceFactoryTest.java
index 213370f3e..0854e4add 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/factory/ServiceFactoryTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/factory/ServiceFactoryTest.java
@@ -22,6 +22,8 @@ import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
import org.apache.knox.gateway.database.AbstractDataSourceFactory;
import org.apache.knox.gateway.database.DatabaseType;
+import org.apache.knox.gateway.database.DerbyDatabaseCredentials;
+import org.apache.knox.gateway.database.EmbeddedDerbyDatabase;
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.Service;
import org.apache.knox.gateway.services.ServiceLifecycleException;
@@ -32,7 +34,6 @@ import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.KeystoreService;
import org.apache.knox.gateway.services.security.MasterService;
-import org.apache.knox.gateway.services.token.impl.DerbyDBTokenStateService;
import org.apache.knox.test.TestUtils;
import org.easymock.EasyMock;
import org.junit.After;
@@ -82,18 +83,18 @@ class ServiceFactoryTest {
final AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
if (expectDbCredentialLookup) {
try {
- aliasService.addAliasForCluster(NO_CLUSTER_NAME, AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME, DerbyDBTokenStateService.DEFAULT_TOKEN_DB_USER_NAME);
+ aliasService.addAliasForCluster(NO_CLUSTER_NAME, AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME, DerbyDatabaseCredentials.DEFAULT_DB_USER_NAME);
EasyMock.expectLastCall().anyTimes();
aliasService.addAliasForCluster(NO_CLUSTER_NAME, AbstractDataSourceFactory.DATABASE_PASSWORD_ALIAS_NAME, masterSecret);
EasyMock.expectLastCall().anyTimes();
- expect(aliasService.getPasswordFromAliasForGateway(AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME)).andReturn(DerbyDBTokenStateService.DEFAULT_TOKEN_DB_USER_NAME.toCharArray()).anyTimes();
+ expect(aliasService.getPasswordFromAliasForGateway(AbstractDataSourceFactory.DATABASE_USER_ALIAS_NAME)).andReturn(DerbyDatabaseCredentials.DEFAULT_DB_USER_NAME.toCharArray()).anyTimes();
expect(aliasService.getPasswordFromAliasForGateway(AbstractDataSourceFactory.DATABASE_PASSWORD_ALIAS_NAME)).andReturn(masterSecret.toCharArray()).anyTimes();
// prepare GatewayConfig
expect(gatewayConfig.getDatabaseType()).andReturn(DatabaseType.DERBY.type()).anyTimes();
tempDbFolder = TestUtils.createTempDir(this.getClass().getName());
expect(gatewayConfig.getGatewaySecurityDir()).andReturn(tempDbFolder.getAbsolutePath()).anyTimes();
- expect(gatewayConfig.getDatabaseName()).andReturn(Paths.get(tempDbFolder.getAbsolutePath(), DerbyDBTokenStateService.DB_NAME).toString()).anyTimes();
+ expect(gatewayConfig.getDatabaseName()).andReturn(Paths.get(tempDbFolder.getAbsolutePath(), EmbeddedDerbyDatabase.DB_NAME).toString()).anyTimes();
} catch (AliasServiceException | IOException e) {
// NOP
}