fix(access): support encrypted database files
This commit is contained in:
parent
a2fd38d542
commit
28ee0ecc8c
|
|
@ -1,5 +1,6 @@
|
|||
dependencies {
|
||||
implementation 'io.github.spannm:ucanaccess:5.1.5'
|
||||
implementation 'io.github.spannm:jackcess-encrypt:5.1.0'
|
||||
}
|
||||
|
||||
tasks.named('shadowJar') {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
public final class AccessAgent extends BaseDatabaseAgent {
|
||||
private static final String ENCRYPTED_ACCESS_OPENER = EncryptedAccessOpener.class.getName();
|
||||
private Connection connection;
|
||||
private String databaseFile = "";
|
||||
|
||||
|
|
@ -285,6 +286,11 @@ public final class AccessAgent extends BaseDatabaseAgent {
|
|||
}
|
||||
rawUrl = appendUCanAccessParams(rawUrl, params.getUrl_params());
|
||||
|
||||
// The codec opener is safe for regular files and enables encrypted Access files when a password is supplied.
|
||||
if (!containsIgnoreCase(rawUrl, "jackcessOpener=")) {
|
||||
rawUrl += (rawUrl.endsWith(";") ? "" : ";") + "jackcessOpener=" + ENCRYPTED_ACCESS_OPENER;
|
||||
}
|
||||
|
||||
if (!createIfMissing || Files.exists(Path.of(databasePath(params)))) {
|
||||
return rawUrl;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
package com.dbx.agent.access;
|
||||
|
||||
import io.github.spannm.jackcess.Database;
|
||||
import io.github.spannm.jackcess.DatabaseBuilder;
|
||||
import io.github.spannm.jackcess.encrypt.CryptCodecProvider;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import net.ucanaccess.jdbc.IJackcessOpenerInterface;
|
||||
|
||||
public final class EncryptedAccessOpener implements IJackcessOpenerInterface {
|
||||
@Override
|
||||
public Database open(File file, String password) throws IOException {
|
||||
DatabaseBuilder builder = new DatabaseBuilder(file);
|
||||
// UCanAccess flushes changes at transaction boundaries, so Jackcess auto-sync is unnecessary overhead.
|
||||
builder.withAutoSync(false);
|
||||
builder.withCodecProvider(new CryptCodecProvider(password));
|
||||
builder.withReadOnly(false);
|
||||
return builder.open();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.dbx.agent.ExecuteQueryOptions;
|
|||
import com.dbx.agent.QueryPageOptions;
|
||||
import com.dbx.agent.test.JdbcConnectedAgentTest;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -66,7 +67,7 @@ class AccessAgentTest extends JdbcConnectedAgentTest {
|
|||
ConnectParams params = new ConnectParams("", 0, file.toString(), "", "", "memory=false;ignoreCase=true", "", false);
|
||||
|
||||
Assertions.assertEquals(
|
||||
"jdbc:ucanaccess://" + file + ";memory=false;ignoreCase=true;newDatabaseVersion=V2010",
|
||||
"jdbc:ucanaccess://" + file + ";memory=false;ignoreCase=true;jackcessOpener=com.dbx.agent.access.EncryptedAccessOpener;newDatabaseVersion=V2010",
|
||||
AccessAgent.jdbcUrl(params, true)
|
||||
);
|
||||
}
|
||||
|
|
@ -77,11 +78,52 @@ class AccessAgentTest extends JdbcConnectedAgentTest {
|
|||
ConnectParams params = new ConnectParams("", 0, "jdbc:ucanaccess://" + file + ";", "", "", ";memory=false", "", false);
|
||||
|
||||
Assertions.assertEquals(
|
||||
"jdbc:ucanaccess://" + file + ";memory=false;newDatabaseVersion=V2010",
|
||||
"jdbc:ucanaccess://" + file + ";memory=false;jackcessOpener=com.dbx.agent.access.EncryptedAccessOpener;newDatabaseVersion=V2010",
|
||||
AccessAgent.jdbcUrl(params, true)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preservesCustomJackcessOpener() {
|
||||
Path file = tempDir.resolve("custom-opener.accdb");
|
||||
ConnectParams params = new ConnectParams(
|
||||
"", 0, file.toString(), "", "", "jackcessOpener=example.CustomOpener", "", false
|
||||
);
|
||||
|
||||
Assertions.assertEquals(
|
||||
"jdbc:ucanaccess://" + file + ";jackcessOpener=example.CustomOpener;newDatabaseVersion=V2010",
|
||||
AccessAgent.jdbcUrl(params, true)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectsToEncryptedAccessFile() throws Exception {
|
||||
Path source = Path.of(getClass().getResource("/db2007-enc.accdb").toURI());
|
||||
Path file = tempDir.resolve("encrypted.accdb");
|
||||
Files.copy(source, file);
|
||||
AccessAgent agent = new AccessAgent();
|
||||
|
||||
try {
|
||||
agent.connect(new ConnectParams("", 0, file.toString(), "", "Test123", "", "", false));
|
||||
Assertions.assertFalse(agent.listTables("").isEmpty());
|
||||
} finally {
|
||||
agent.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsIncorrectEncryptedAccessPassword() throws Exception {
|
||||
Path source = Path.of(getClass().getResource("/db2007-enc.accdb").toURI());
|
||||
Path file = tempDir.resolve("encrypted-wrong-password.accdb");
|
||||
Files.copy(source, file);
|
||||
AccessAgent agent = new AccessAgent();
|
||||
|
||||
RuntimeException error = Assertions.assertThrows(RuntimeException.class, () ->
|
||||
agent.connect(new ConnectParams("", 0, file.toString(), "", "wrong", "", "", false))
|
||||
);
|
||||
Assertions.assertTrue(error.getMessage().contains("Incorrect password provided"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void supportsLimitOffsetSqlAndCursorResultPages() {
|
||||
withAgent("pages", agent -> {
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue