Fix building of MongoDB connection string with authentication db

This commit restores the previous behavior of MongoDB auto-
configuration, where the value of the
`spring.data.mongodb.authentication-database` is set in the
credentials when provided.

Fixes gh-35567
This commit is contained in:
Scott Frederick
2023-05-19 16:22:15 -05:00
parent 593fa1dc42
commit a02eb48528
4 changed files with 269 additions and 15 deletions

View File

@@ -16,6 +16,9 @@
package org.springframework.boot.autoconfigure.mongo;
import java.util.ArrayList;
import java.util.List;
import com.mongodb.ConnectionString;
/**
@@ -24,6 +27,7 @@ import com.mongodb.ConnectionString;
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Scott Frederick
* @since 3.1.0
*/
public class PropertiesMongoConnectionDetails implements MongoConnectionDetails {
@@ -44,7 +48,9 @@ public class PropertiesMongoConnectionDetails implements MongoConnectionDetails
if (this.properties.getUsername() != null) {
builder.append(this.properties.getUsername());
builder.append(":");
builder.append(this.properties.getPassword());
if (this.properties.getPassword() != null) {
builder.append(this.properties.getPassword());
}
builder.append("@");
}
builder.append((this.properties.getHost() != null) ? this.properties.getHost() : "localhost");
@@ -55,20 +61,12 @@ public class PropertiesMongoConnectionDetails implements MongoConnectionDetails
if (this.properties.getAdditionalHosts() != null) {
builder.append(String.join(",", this.properties.getAdditionalHosts()));
}
if (this.properties.getMongoClientDatabase() != null || this.properties.getReplicaSetName() != null
|| this.properties.getAuthenticationDatabase() != null) {
builder.append("/");
if (this.properties.getMongoClientDatabase() != null) {
builder.append(this.properties.getMongoClientDatabase());
}
else if (this.properties.getAuthenticationDatabase() != null) {
builder.append(this.properties.getAuthenticationDatabase());
}
if (this.properties.getReplicaSetName() != null) {
builder.append("?");
builder.append("replicaSet=");
builder.append(this.properties.getReplicaSetName());
}
builder.append("/");
builder.append(this.properties.getMongoClientDatabase());
List<String> options = getOptions();
if (!options.isEmpty()) {
builder.append("?");
builder.append(String.join("&", options));
}
return new ConnectionString(builder.toString());
}
@@ -79,4 +77,15 @@ public class PropertiesMongoConnectionDetails implements MongoConnectionDetails
PropertiesMongoConnectionDetails.this.properties.getGridfs().getBucket());
}
private List<String> getOptions() {
List<String> options = new ArrayList<>();
if (this.properties.getReplicaSetName() != null) {
options.add("replicaSet=" + this.properties.getReplicaSetName());
}
if (this.properties.getUsername() != null && this.properties.getAuthenticationDatabase() != null) {
options.add("authSource=" + this.properties.getAuthenticationDatabase());
}
return options;
}
}

View File

@@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.internal.MongoClientImpl;
@@ -107,6 +108,95 @@ class MongoAutoConfigurationTests {
});
}
@Test
void doesNotConfigureCredentialsWithoutUsername() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.password=secret",
"spring.data.mongodb.authentication-database=authdb")
.run((context) -> assertThat(getSettings(context).getCredential()).isNull());
}
@Test
void configuresCredentialsFromPropertiesWithDefaultDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("test");
});
}
@Test
void configuresCredentialsFromPropertiesWithDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret",
"spring.data.mongodb.database=mydb")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("mydb");
});
}
@Test
void configuresCredentialsFromPropertiesWithAuthDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret",
"spring.data.mongodb.database=mydb", "spring.data.mongodb.authentication-database=authdb")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("authdb");
});
}
@Test
void doesNotConfigureCredentialsWithoutUsernameInUri() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=mongodb://localhost/mydb?authSource=authdb")
.run((context) -> assertThat(getSettings(context).getCredential()).isNull());
}
@Test
void configuresCredentialsFromUriPropertyWithDefaultDatabase() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("admin");
});
}
@Test
void configuresCredentialsFromUriPropertyWithDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/mydb",
"spring.data.mongodb.database=notused", "spring.data.mongodb.authentication-database=notused")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("mydb");
});
}
@Test
void configuresCredentialsFromUriPropertyWithAuthDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/mydb?authSource=authdb",
"spring.data.mongodb.database=notused", "spring.data.mongodb.authentication-database=notused")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("authdb");
});
}
@Test
void configuresSingleClient() {
this.contextRunner.withUserConfiguration(FallbackMongoClientConfig.class)

View File

@@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicReference;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.ReadPreference;
import com.mongodb.connection.AsynchronousSocketChannelStreamFactoryFactory;
import com.mongodb.connection.SslSettings;
@@ -121,6 +122,95 @@ class MongoReactiveAutoConfigurationTests {
});
}
@Test
void doesNotConfigureCredentialsWithoutUsername() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.password=secret",
"spring.data.mongodb.authentication-database=authdb")
.run((context) -> assertThat(getSettings(context).getCredential()).isNull());
}
@Test
void configuresCredentialsFromPropertiesWithDefaultDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("test");
});
}
@Test
void configuresCredentialsFromPropertiesWithDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret",
"spring.data.mongodb.database=mydb")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("mydb");
});
}
@Test
void configuresCredentialsFromPropertiesWithAuthDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.username=user", "spring.data.mongodb.password=secret",
"spring.data.mongodb.database=mydb", "spring.data.mongodb.authentication-database=authdb")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("authdb");
});
}
@Test
void doesNotConfigureCredentialsWithoutUsernameInUri() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=mongodb://localhost/mydb?authSource=authdb")
.run((context) -> assertThat(getSettings(context).getCredential()).isNull());
}
@Test
void configuresCredentialsFromUriPropertyWithDefaultDatabase() {
this.contextRunner.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("admin");
});
}
@Test
void configuresCredentialsFromUriPropertyWithDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/mydb",
"spring.data.mongodb.database=notused", "spring.data.mongodb.authentication-database=notused")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("mydb");
});
}
@Test
void configuresCredentialsFromUriPropertyWithAuthDatabase() {
this.contextRunner
.withPropertyValues("spring.data.mongodb.uri=mongodb://user:secret@localhost/mydb?authSource=authdb",
"spring.data.mongodb.database=notused", "spring.data.mongodb.authentication-database=notused")
.run((context) -> {
MongoCredential credential = getSettings(context).getCredential();
assertThat(credential.getUserName()).isEqualTo("user");
assertThat(credential.getPassword()).isEqualTo("secret".toCharArray());
assertThat(credential.getSource()).isEqualTo("authdb");
});
}
@Test
void nettyStreamFactoryFactoryIsConfiguredAutomatically() {
AtomicReference<EventLoopGroup> eventLoopGroupReference = new AtomicReference<>();

View File

@@ -25,11 +25,66 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link PropertiesMongoConnectionDetails}.
*
* @author Christoph Dreis
* @author Scott Frederick
*/
class PropertiesMongoConnectionDetailsTests {
private final MongoProperties properties = new MongoProperties();
@Test
void credentialsCanBeConfiguredWithUsername() {
this.properties.setUsername("user");
ConnectionString connectionString = getConnectionString();
assertThat(connectionString.getUsername()).isEqualTo("user");
assertThat(connectionString.getPassword()).isEmpty();
assertThat(connectionString.getCredential().getUserName()).isEqualTo("user");
assertThat(connectionString.getCredential().getPassword()).isEmpty();
assertThat(connectionString.getCredential().getSource()).isEqualTo("test");
}
@Test
void credentialsCanBeConfiguredWithUsernameAndPassword() {
this.properties.setUsername("user");
this.properties.setPassword("secret".toCharArray());
ConnectionString connectionString = getConnectionString();
assertThat(connectionString.getUsername()).isEqualTo("user");
assertThat(connectionString.getPassword()).isEqualTo("secret".toCharArray());
assertThat(connectionString.getCredential().getUserName()).isEqualTo("user");
assertThat(connectionString.getCredential().getPassword()).isEqualTo("secret".toCharArray());
assertThat(connectionString.getCredential().getSource()).isEqualTo("test");
}
@Test
void databaseCanBeConfigured() {
this.properties.setDatabase("db");
ConnectionString connectionString = getConnectionString();
assertThat(connectionString.getDatabase()).isEqualTo("db");
}
@Test
void databaseHasDefaultWhenNotConfigured() {
ConnectionString connectionString = getConnectionString();
assertThat(connectionString.getDatabase()).isEqualTo("test");
}
@Test
void authenticationDatabaseCanBeConfigured() {
this.properties.setUsername("user");
this.properties.setDatabase("db");
this.properties.setAuthenticationDatabase("authdb");
ConnectionString connectionString = getConnectionString();
assertThat(connectionString.getDatabase()).isEqualTo("db");
assertThat(connectionString.getCredential().getSource()).isEqualTo("authdb");
assertThat(connectionString.getCredential().getUserName()).isEqualTo("user");
}
@Test
void authenticationDatabaseIsNotConfiguredWhenUsernameIsNotConfigured() {
this.properties.setAuthenticationDatabase("authdb");
ConnectionString connectionString = getConnectionString();
assertThat(connectionString.getCredential()).isNull();
}
@Test
void replicaSetCanBeConfigured() {
this.properties.setReplicaSetName("test");
@@ -37,6 +92,16 @@ class PropertiesMongoConnectionDetailsTests {
assertThat(connectionString.getRequiredReplicaSetName()).isEqualTo("test");
}
@Test
void replicaSetCanBeConfiguredWithDatabase() {
this.properties.setUsername("user");
this.properties.setDatabase("db");
this.properties.setReplicaSetName("test");
ConnectionString connectionString = getConnectionString();
assertThat(connectionString.getDatabase()).isEqualTo("db");
assertThat(connectionString.getRequiredReplicaSetName()).isEqualTo("test");
}
private PropertiesMongoConnectionDetails createConnectionDetails() {
return new PropertiesMongoConnectionDetails(this.properties);
}