Skip to content

Commit

Permalink
Implement OIDC map value splitting
Browse files Browse the repository at this point in the history
  • Loading branch information
katcharov committed Apr 26, 2024
1 parent bc30a2f commit d856d84
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 6 deletions.
24 changes: 20 additions & 4 deletions driver-core/src/main/com/mongodb/ConnectionString.java
Expand Up @@ -38,6 +38,7 @@
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -923,6 +924,9 @@ private MongoCredential createCredentials(final Map<String, List<String>> option
}
String key = mechanismPropertyKeyValue[0].trim().toLowerCase();
String value = mechanismPropertyKeyValue[1].trim();
if (!decodeWholeOptionValue(mechanism)) {
value = urldecode(value);
}
if (MECHANISM_KEYS_DISALLOWED_IN_CONNECTION_STRING.contains(key)) {
throw new IllegalArgumentException(format("The connection string contains disallowed mechanism properties. "
+ "'%s' must be set on the credential programmatically.", key));
Expand All @@ -938,6 +942,13 @@ private MongoCredential createCredentials(final Map<String, List<String>> option
return credential;
}

private static boolean decodeWholeOptionValue(final AuthenticationMechanism mechanism) {
return !AuthenticationMechanism.MONGODB_OIDC.equals(mechanism);
}
private static boolean decodeWholeOptionValue(final List<String> options) {
return !options.contains("authMechanism=" + AuthenticationMechanism.MONGODB_OIDC.getMechanismName());
}

private MongoCredential createMongoCredentialWithMechanism(final AuthenticationMechanism mechanism, final String userName,
@Nullable final char[] password,
@Nullable final String authSource,
Expand Down Expand Up @@ -1018,12 +1029,14 @@ private String getLastValue(final Map<String, List<String>> optionsMap, final St

private Map<String, List<String>> parseOptions(final String optionsPart) {
Map<String, List<String>> optionsMap = new HashMap<>();
if (optionsPart.length() == 0) {
if (optionsPart.isEmpty()) {
return optionsMap;
}

for (final String part : optionsPart.split("&|;")) {
if (part.length() == 0) {
List<String> options = Arrays.asList(optionsPart.split("&|;"));
boolean decodeWholeOptionValue = decodeWholeOptionValue(options);
for (final String part : options) {
if (part.isEmpty()) {
continue;
}
int idx = part.indexOf("=");
Expand All @@ -1034,7 +1047,10 @@ private Map<String, List<String>> parseOptions(final String optionsPart) {
if (valueList == null) {
valueList = new ArrayList<>(1);
}
valueList.add(urldecode(value));
if (decodeWholeOptionValue) {
value = urldecode(value);
}
valueList.add(value);
optionsMap.put(key, valueList);
} else {
throw new IllegalArgumentException(format("The connection string contains an invalid option '%s'. "
Expand Down
70 changes: 68 additions & 2 deletions driver-core/src/test/resources/auth/legacy/connection-string.json
Expand Up @@ -474,13 +474,13 @@
}
},
{
"description": "should throw an exception if supplied a password (MONGODB-OIDC)",
"description": "should throw an exception if username and password is specified for test environment (MONGODB-OIDC)",
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:test",
"valid": false,
"credential": null
},
{
"description": "should throw an exception if username is specified for test (MONGODB-OIDC)",
"description": "should throw an exception if username is specified for test environment (MONGODB-OIDC)",
"uri": "mongodb://principalName@localhost/?authMechanism=MONGODB-OIDC&ENVIRONMENT:test",
"valid": false,
"credential": null
Expand All @@ -503,6 +503,12 @@
"valid": false,
"credential": null
},
{
"description": "should throw an exception if neither provider nor callbacks specified (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC",
"valid": false,
"credential": null
},
{
"description": "should recognise the mechanism with azure provider (MONGODB-OIDC)",
"uri": "mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo",
Expand Down Expand Up @@ -533,6 +539,66 @@
}
}
},
{
"description": "should accept a url-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb%3A%2F%2Ftest-cluster",
"valid": true,
"credential": {
"username": "user",
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "azure",
"TOKEN_RESOURCE": "mongodb://test-cluster"
}
}
},
{
"description": "should accept an un-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:mongodb://test-cluster",
"valid": true,
"credential": {
"username": "user",
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "azure",
"TOKEN_RESOURCE": "mongodb://test-cluster"
}
}
},
{
"description": "should handle a complicated url-encoded TOKEN_RESOURCE (MONGODB-OIDC)",
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:abc%2Cd%25ef%3Ag%26hi",
"valid": true,
"credential": {
"username": "user",
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "azure",
"TOKEN_RESOURCE": "abc,d%ef:g&hi"
}
}
},
{
"description": "should url-encode a TOKEN_RESOURCE (MONGODB-OIDC)",
"uri": "mongodb://user@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:a$b",
"valid": true,
"credential": {
"username": "user",
"password": null,
"source": "$external",
"mechanism": "MONGODB-OIDC",
"mechanism_properties": {
"ENVIRONMENT": "azure",
"TOKEN_RESOURCE": "a$b"
}
}
},
{
"description": "should accept a username and throw an error for a password with azure provider (MONGODB-OIDC)",
"uri": "mongodb://user:pass@localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties=ENVIRONMENT:azure,TOKEN_RESOURCE:foo",
Expand Down
Expand Up @@ -20,6 +20,10 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
Expand All @@ -34,6 +38,31 @@ void defaults() {
assertAll(() -> assertNull(connectionStringDefault.getServerMonitoringMode()));
}

@Test
public void mustDecodeOidcIndividually() {
String string = "abc,d!@#$%^&*;ef:ghi";
ConnectionString cs = new ConnectionString(
"mongodb://localhost/?authMechanism=MONGODB-OIDC&authMechanismProperties="
+ "ENVIRONMENT:azure,TOKEN_RESOURCE:" + encode(string));
assertEquals(string, cs.getCredential().getMechanismProperty("TOKEN_RESOURCE", null));
}

@Test
public void mustDecodeNonOidcAsWhole() {
ConnectionString cs2 = new ConnectionString(
"mongodb://foo:bar@example.com/?authMechanism=GSSAPI&authMechanismProperties="
+ encode("SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authSource=$external"));
assertEquals("other", cs2.getCredential().getMechanismProperty("SERVICE_NAME", null));
}

private static String encode(final String string) {
try {
return URLEncoder.encode(string, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

@ParameterizedTest
@ValueSource(strings = {DEFAULT_OPTIONS + "serverMonitoringMode=stream"})
void equalAndHashCode(final String connectionString) {
Expand Down

0 comments on commit d856d84

Please sign in to comment.