Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ repositories {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.mongodb:mongodb-driver-sync:4.11.1"
implementation "org.mongodb:mongodb-driver-sync:5.6.1"
implementation group: 'org.jetbrains', name: 'annotations', version: '15.0'
implementation group: 'org.apache.commons', name: 'commons-text', version: '1.10.0'
implementation group: 'org.graalvm.js', name: 'js', version: '22.3.1'
implementation files('libs/JMongosh-0.9.jar')
implementation group: 'com.nimbusds', name: 'oauth2-oidc-sdk', version: '11.+'
implementation group: 'org.graalvm.polyglot', name: 'polyglot', version: '25.0.1'
implementation group: 'org.graalvm.js', name: 'js', version: '25.0.1'
implementation files('libs/JMongosh-0.9.1.jar')
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/com/dbschema/MongoJdbcDriver.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package com.dbschema;

import com.dbschema.mongo.DriverPropertyInfoHelper;
import com.dbschema.mongo.MongoClientWrapper;
import com.dbschema.mongo.MongoConnection;
import com.dbschema.mongo.MongoService;
import com.dbschema.mongo.mongosh.LazyShellHolder;
import com.dbschema.mongo.mongosh.PrecalculatingShellHolder;
import com.dbschema.mongo.mongosh.ShellHolder;
import com.dbschema.mongo.oidc.OidcCallback;
import com.mongodb.MongoCredential;
import com.mongodb.mongosh.MongoShell;
import org.graalvm.polyglot.Engine;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.sql.*;
import java.util.Optional;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.logging.Logger;
Expand All @@ -33,6 +38,7 @@ public class MongoJdbcDriver implements Driver {
private @Nullable ExecutorService executorService;
private @Nullable Engine sharedEngine;
private @NotNull ShellHolder shellHolder;
private MongoConnection mongoConnection;

static {
try {
Expand Down Expand Up @@ -100,7 +106,16 @@ public Connection connect(String url, Properties info) throws SQLException {
synchronized (this) {
ShellHolder shellHolder = this.shellHolder;
this.shellHolder = createShellHolder();
return new MongoConnection(url, info, username, password, fetchDocumentsForMeta, shellHolder);
MongoCredential.OidcCallbackContext existingResult = Optional.ofNullable(this.mongoConnection)
.map(MongoConnection::getService)
.map(MongoService::getClient)
.map(MongoClientWrapper::getOidcCallback)
.map(OidcCallback::getCallbackContext)
.orElse(null);

this.mongoConnection = new MongoConnection(url, info, username, password, fetchDocumentsForMeta, shellHolder, existingResult);

return this.mongoConnection;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

public class DriverPropertyInfoHelper {
public static final String AUTH_MECHANISM = "authMechanism";
public static final String[] AUTH_MECHANISM_CHOICES = new String[]{"GSSAPI", "MONGODB-AWS", "MONGODB-X509", "PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-256"};
public static final String[] AUTH_MECHANISM_CHOICES = new String[]{"GSSAPI", "MONGODB-AWS", "MONGODB-X509", "PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-256", "MONGODB-OIDC"};
public static final String AUTH_SOURCE = "authSource";
public static final String AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN";
public static final String SERVICE_NAME = "SERVICE_NAME";
Expand Down
168 changes: 100 additions & 68 deletions src/main/java/com/dbschema/mongo/MongoClientWrapper.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.dbschema.mongo;

import com.dbschema.mongo.oidc.OidcCallback;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.ServerApi;
import com.mongodb.ServerApiVersion;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;
Expand All @@ -26,79 +30,103 @@ public class MongoClientWrapper implements AutoCloseable {
private boolean isClosed = false;
private final MongoClient mongoClient;
public final String databaseNameFromUrl;
public final OidcCallback oidcCallback;

public MongoClientWrapper(@NotNull String uri, @NotNull Properties prop, @Nullable String username, @Nullable String password) throws SQLException {
public MongoClientWrapper(@NotNull String uri, @NotNull Properties prop, @Nullable String username, @Nullable String password, @Nullable MongoCredential.OidcCallbackContext callbackContext) throws SQLException {
this.oidcCallback = new OidcCallback(callbackContext);
try {
boolean automaticEncoding = ENCODE_CREDENTIALS_DEFAULT;
if (prop.getProperty(ENCODE_CREDENTIALS) != null) {
automaticEncoding = Boolean.parseBoolean(prop.getProperty(ENCODE_CREDENTIALS));
}
boolean automaticEncoding = ENCODE_CREDENTIALS_DEFAULT;
if (prop.getProperty(ENCODE_CREDENTIALS) != null) {
automaticEncoding = Boolean.parseBoolean(prop.getProperty(ENCODE_CREDENTIALS));
}

uri = insertCredentials(uri, username, password, automaticEncoding);
uri = insertAuthMechanism(uri, prop.getProperty(AUTH_MECHANISM));
uri = insertAuthSource(uri, prop.getProperty(AUTH_SOURCE));
uri = insertAuthProperty(uri, AWS_SESSION_TOKEN, prop.getProperty(AWS_SESSION_TOKEN));
uri = insertAuthProperty(uri, SERVICE_NAME, prop.getProperty(SERVICE_NAME));
uri = insertAuthProperty(uri, SERVICE_REALM, prop.getProperty(SERVICE_REALM));
String canonicalizeHostName = prop.getProperty(CANONICALIZE_HOST_NAME);
if (Boolean.TRUE.toString().equalsIgnoreCase(canonicalizeHostName) || Boolean.FALSE.toString().equalsIgnoreCase(canonicalizeHostName)) {
uri = insertAuthProperty(uri, CANONICALIZE_HOST_NAME, canonicalizeHostName);
}
else if (canonicalizeHostName != null) {
System.err.println("Unknown " + CANONICALIZE_HOST_NAME + " value. Must be true or false.");
}
uri = insertRetryWrites(uri, prop.getProperty(RETRY_WRITES));
uri = insertCredentials(uri, username, password, automaticEncoding);
uri = insertAuthMechanism(uri, prop.getProperty(AUTH_MECHANISM));
uri = insertAuthSource(uri, prop.getProperty(AUTH_SOURCE));
uri = insertAuthProperty(uri, AWS_SESSION_TOKEN, prop.getProperty(AWS_SESSION_TOKEN));
uri = insertAuthProperty(uri, SERVICE_NAME, prop.getProperty(SERVICE_NAME));
uri = insertAuthProperty(uri, SERVICE_REALM, prop.getProperty(SERVICE_REALM));
String canonicalizeHostName = prop.getProperty(CANONICALIZE_HOST_NAME);
if (Boolean.TRUE.toString().equalsIgnoreCase(canonicalizeHostName) || Boolean.FALSE.toString().equalsIgnoreCase(canonicalizeHostName)) {
uri = insertAuthProperty(uri, CANONICALIZE_HOST_NAME, canonicalizeHostName);
}
else if (canonicalizeHostName != null) {
System.err.println("Unknown " + CANONICALIZE_HOST_NAME + " value. Must be true or false.");
}
uri = insertRetryWrites(uri, prop.getProperty(RETRY_WRITES));


// Construct a ServerApi instance using the ServerApi.builder() method
ServerApi serverApi = ServerApi.builder()
.version(ServerApiVersion.V1)
.build();

ConnectionString connectionString = new ConnectionString(uri);
databaseNameFromUrl = connectionString.getDatabase();
int maxPoolSize = getMaxPoolSize(prop);
MongoClientSettings.Builder builder = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.applyToConnectionPoolSettings(b -> b.maxSize(maxPoolSize));
String application = prop.getProperty(APPLICATION_NAME);
if (!isNullOrEmpty(application)) {
builder.applicationName(application);
}
if ("true".equals(prop.getProperty("ssl"))) {
boolean allowInvalidCertificates = uri.contains("tlsAllowInvalidCertificates=true") || uri.contains("sslAllowInvalidCertificates=true")
|| isTrue(prop.getProperty(ALLOW_INVALID_CERTIFICATES, Boolean.toString(ALLOW_INVALID_CERTIFICATES_DEFAULT)));
builder.applyToSslSettings(s -> {
s.enabled(true);
boolean allowInvalidHostnames = isTrue(prop.getProperty(ALLOW_INVALID_HOSTNAMES, Boolean.toString(ALLOW_INVALID_HOSTNAMES_DEFAULT)));
if (allowInvalidHostnames) s.invalidHostNameAllowed(true);
if (allowInvalidCertificates) {
String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
String keyStoreUrl = System.getProperty("javax.net.ssl.keyStore", "");
// check keyStoreUrl
if (!isNullOrEmpty(keyStoreUrl)) {
try {
new URL(keyStoreUrl);
} catch (MalformedURLException e) {
keyStoreUrl = "file:" + keyStoreUrl;
}
}
try {
s.context(getTrustEverybodySSLContext(keyStoreUrl, keyStoreType, keyStorePassword));
}
catch (SSLUtil.SSLParamsException e) {
throw new RuntimeException(e);
}
}
});
}
if (connectionString.getUuidRepresentation() == null) {
String uuidRepresentation = prop.getProperty(UUID_REPRESENTATION, UUID_REPRESENTATION_DEFAULT);
builder.uuidRepresentation(createUuidRepresentation(uuidRepresentation));
}
if (connectionString.getServerSelectionTimeout() == null) {
int timeout = Integer.parseInt(prop.getProperty(SERVER_SELECTION_TIMEOUT, SERVER_SELECTION_TIMEOUT_DEFAULT));
builder.applyToClusterSettings(b -> b.serverSelectionTimeout(timeout, TimeUnit.MILLISECONDS));
}
if (connectionString.getConnectTimeout() == null) {
int timeout = Integer.parseInt(prop.getProperty(CONNECT_TIMEOUT, CONNECT_TIMEOUT_DEFAULT));
builder.applyToSocketSettings(b -> b.connectTimeout(timeout, TimeUnit.MILLISECONDS));
}

MongoCredential credential;

credential =
MongoCredential.createOidcCredential(
connectionString.getUsername())
.withMechanismProperty(
MongoCredential.OIDC_HUMAN_CALLBACK_KEY, oidcCallback);


databaseNameFromUrl = connectionString.getDatabase();
MongoClientSettings.Builder builder = MongoClientSettings.builder()
.applyConnectionString(connectionString)
.serverApi(serverApi)
.credential(credential)
.uuidRepresentation(createUuidRepresentation(prop.getProperty(UUID_REPRESENTATION, UUID_REPRESENTATION_DEFAULT)))
.applyToConnectionPoolSettings(b -> b.maxSize(getMaxPoolSize(prop)))

.applyToSocketSettings(b -> b.connectTimeout(Integer.parseInt(prop.getProperty(CONNECT_TIMEOUT, CONNECT_TIMEOUT_DEFAULT)), TimeUnit.MILLISECONDS));

String application = prop.getProperty(APPLICATION_NAME);
if (!isNullOrEmpty(application)) {
builder.applicationName(application);
}
if ("true".equals(prop.getProperty("ssl"))) {
boolean allowInvalidCertificates = uri.contains("tlsAllowInvalidCertificates=true") || uri.contains("sslAllowInvalidCertificates=true")
|| isTrue(prop.getProperty(ALLOW_INVALID_CERTIFICATES, Boolean.toString(ALLOW_INVALID_CERTIFICATES_DEFAULT)));
builder.applyToSslSettings(s -> {
s.enabled(true);
boolean allowInvalidHostnames = isTrue(prop.getProperty(ALLOW_INVALID_HOSTNAMES, Boolean.toString(ALLOW_INVALID_HOSTNAMES_DEFAULT)));
if (allowInvalidHostnames) s.invalidHostNameAllowed(true);
if (allowInvalidCertificates) {
String keyStoreType = System.getProperty("javax.net.ssl.keyStoreType", KeyStore.getDefaultType());
String keyStorePassword = System.getProperty("javax.net.ssl.keyStorePassword", "");
String keyStoreUrl = System.getProperty("javax.net.ssl.keyStore", "");
// check keyStoreUrl
if (!isNullOrEmpty(keyStoreUrl)) {
try {
new URL(keyStoreUrl);
} catch (MalformedURLException e) {
keyStoreUrl = "file:" + keyStoreUrl;
}
}
try {
s.context(getTrustEverybodySSLContext(keyStoreUrl, keyStoreType, keyStorePassword));
}
catch (SSLUtil.SSLParamsException e) {
throw new RuntimeException(e);
}
}
});
}
if (connectionString.getUuidRepresentation() == null) {
String uuidRepresentation = prop.getProperty(UUID_REPRESENTATION, UUID_REPRESENTATION_DEFAULT);
builder.uuidRepresentation(createUuidRepresentation(uuidRepresentation));
}
if (connectionString.getServerSelectionTimeout() == null) {
int timeout = Integer.parseInt(prop.getProperty(SERVER_SELECTION_TIMEOUT, SERVER_SELECTION_TIMEOUT_DEFAULT));
builder.applyToClusterSettings(b -> b.serverSelectionTimeout(timeout, TimeUnit.MILLISECONDS));
}
if (connectionString.getConnectTimeout() == null) {
int timeout = Integer.parseInt(prop.getProperty(CONNECT_TIMEOUT, CONNECT_TIMEOUT_DEFAULT));
builder.applyToSocketSettings(b -> b.connectTimeout(timeout, TimeUnit.MILLISECONDS));
}

this.mongoClient = MongoClients.create(builder.build());
}
catch (Exception e) {
Expand Down Expand Up @@ -160,6 +188,10 @@ public MongoDatabase getDatabase(String databaseName) throws SQLAlreadyClosedExc
return mongoClient.getDatabase(databaseName);
}

public OidcCallback getOidcCallback() {
return this.oidcCallback;
}

@NotNull
public MongoClient getMongoClient() {
return mongoClient;
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/com/dbschema/mongo/MongoConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.dbschema.mongo.mongosh.MongoshScriptEngine;
import com.dbschema.mongo.mongosh.PrecalculatingShellHolder;
import com.dbschema.mongo.mongosh.ShellHolder;
import com.mongodb.MongoCredential;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -17,14 +18,17 @@ public class MongoConnection implements Connection {
private String schema;
private boolean isClosed = false;
private boolean isReadOnly = false;
private MongoCredential.OidcCallbackContext oidcCallbackContext;

public MongoConnection(@NotNull String url,
@NotNull Properties info,
@Nullable String username,
@Nullable String password,
int fetchDocumentsForMeta,
@NotNull ShellHolder shellHolder) throws SQLException {
this.service = new MongoService(url, info, username, password, fetchDocumentsForMeta);
@NotNull ShellHolder shellHolder,
@Nullable MongoCredential.OidcCallbackContext callbackContext) throws SQLException {
this.oidcCallbackContext = callbackContext;
this.service = new MongoService(url, info, username, password, fetchDocumentsForMeta, this.oidcCallbackContext);
this.scriptEngine = new MongoshScriptEngine(this, shellHolder);
try {
setSchema(service.getDatabaseNameFromUrl());
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/dbschema/mongo/MongoService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dbschema.mongo;

import com.dbschema.mongo.schema.MetaCollection;
import com.mongodb.MongoCredential;
import com.mongodb.MongoSecurityException;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
Expand All @@ -26,10 +27,10 @@ public class MongoService implements AutoCloseable {


public MongoService(@NotNull String uri, @NotNull Properties prop, @Nullable String username,
@Nullable String password, int fetchDocumentsForMeta) throws SQLException {
@Nullable String password, int fetchDocumentsForMeta, @Nullable MongoCredential.OidcCallbackContext callbackContext) throws SQLException {
this.uri = uri;
this.fetchDocumentsForMeta = fetchDocumentsForMeta;
client = new MongoClientWrapper(uri, prop, username, password);
client = new MongoClientWrapper(uri, prop, username, password, callbackContext);
}

public MongoClientWrapper getClient() {
Expand Down
Loading