38
38
import java .net .URLDecoder ;
39
39
import java .nio .charset .StandardCharsets ;
40
40
import java .util .ArrayList ;
41
+ import java .util .Arrays ;
41
42
import java .util .Collections ;
42
43
import java .util .HashMap ;
43
44
import java .util .HashSet ;
229
230
* </ul>
230
231
* <p>Authentication configuration:</p>
231
232
* <ul>
232
- * <li>{@code authMechanism=MONGO-CR|GSSAPI|PLAIN|MONGODB-X509}: The authentication mechanism to use if a credential was supplied.
233
+ * <li>{@code authMechanism=MONGO-CR|GSSAPI|PLAIN|MONGODB-X509|MONGODB-OIDC }: The authentication mechanism to use if a credential was supplied.
233
234
* The default is unspecified, in which case the client will pick the most secure mechanism available based on the sever version. For the
234
- * GSSAPI and MONGODB-X509 mechanisms, no password is accepted, only the username.
235
+ * GSSAPI, MONGODB-X509, and MONGODB-OIDC mechanisms, no password is accepted, only the username.
235
236
* </li>
236
237
* <li>{@code authSource=string}: The source of the authentication credentials. This is typically the database that
237
238
* the credentials have been created. The value defaults to the database specified in the path portion of the connection string.
238
239
* If the database is specified in neither place, the default value is "admin". This option is only respected when using the MONGO-CR
239
240
* mechanism (the default).
240
241
* </li>
241
242
* <li>{@code authMechanismProperties=PROPERTY_NAME:PROPERTY_VALUE,PROPERTY_NAME2:PROPERTY_VALUE2}: This option allows authentication
242
- * mechanism properties to be set on the connection string.
243
+ * mechanism properties to be set on the connection string. Property values must be percent-encoded individually, when
244
+ * separator or escape characters are used (including {@code ,} (comma), {@code =}, {@code +}, {@code &}, and {@code %}). The
245
+ * entire substring following the {@code =} should not itself be encoded.
243
246
* </li>
244
247
* <li>{@code gssapiServiceName=string}: This option only applies to the GSSAPI mechanism and is used to alter the service name.
245
248
* Deprecated, please use {@code authMechanismProperties=SERVICE_NAME:string} instead.
@@ -916,13 +919,16 @@ private MongoCredential createCredentials(final Map<String, List<String>> option
916
919
917
920
if (credential != null && authMechanismProperties != null ) {
918
921
for (String part : authMechanismProperties .split ("," )) {
919
- String [] mechanismPropertyKeyValue = part .split (":" );
922
+ String [] mechanismPropertyKeyValue = part .split (":" , 2 );
920
923
if (mechanismPropertyKeyValue .length != 2 ) {
921
924
throw new IllegalArgumentException (format ("The connection string contains invalid authentication properties. "
922
925
+ "'%s' is not a key value pair" , part ));
923
926
}
924
927
String key = mechanismPropertyKeyValue [0 ].trim ().toLowerCase ();
925
928
String value = mechanismPropertyKeyValue [1 ].trim ();
929
+ if (decodeValueOfKeyValuePair (credential .getMechanism ())) {
930
+ value = urldecode (value );
931
+ }
926
932
if (MECHANISM_KEYS_DISALLOWED_IN_CONNECTION_STRING .contains (key )) {
927
933
throw new IllegalArgumentException (format ("The connection string contains disallowed mechanism properties. "
928
934
+ "'%s' must be set on the credential programmatically." , key ));
@@ -938,6 +944,27 @@ private MongoCredential createCredentials(final Map<String, List<String>> option
938
944
return credential ;
939
945
}
940
946
947
+ private static boolean decodeWholeOptionValue (final boolean isOidc , final String key ) {
948
+ // The "whole option value" is the entire string following = in an option,
949
+ // including separators when the value is a list or list of key-values.
950
+ // This is the original parsing behaviour, but implies that users can
951
+ // encode separators (much like they might with URL parameters). This
952
+ // behaviour implies that users cannot encode "key-value" values that
953
+ // contain a comma, because this will (after this "whole value decoding)
954
+ // be parsed as a key-value separator, rather than part of a value.
955
+ return !(isOidc && key .equals ("authmechanismproperties" ));
956
+ }
957
+
958
+ private static boolean decodeValueOfKeyValuePair (@ Nullable final String mechanismName ) {
959
+ // Only authMechanismProperties should be individually decoded, and only
960
+ // when the mechanism is OIDC. These will not have been decoded.
961
+ return AuthenticationMechanism .MONGODB_OIDC .getMechanismName ().equals (mechanismName );
962
+ }
963
+
964
+ private static boolean isOidc (final List <String > options ) {
965
+ return options .contains ("authMechanism=" + AuthenticationMechanism .MONGODB_OIDC .getMechanismName ());
966
+ }
967
+
941
968
private MongoCredential createMongoCredentialWithMechanism (final AuthenticationMechanism mechanism , final String userName ,
942
969
@ Nullable final char [] password ,
943
970
@ Nullable final String authSource ,
@@ -1018,12 +1045,14 @@ private String getLastValue(final Map<String, List<String>> optionsMap, final St
1018
1045
1019
1046
private Map <String , List <String >> parseOptions (final String optionsPart ) {
1020
1047
Map <String , List <String >> optionsMap = new HashMap <>();
1021
- if (optionsPart .length () == 0 ) {
1048
+ if (optionsPart .isEmpty () ) {
1022
1049
return optionsMap ;
1023
1050
}
1024
1051
1025
- for (final String part : optionsPart .split ("&|;" )) {
1026
- if (part .length () == 0 ) {
1052
+ List <String > options = Arrays .asList (optionsPart .split ("&|;" ));
1053
+ boolean isOidc = isOidc (options );
1054
+ for (final String part : options ) {
1055
+ if (part .isEmpty ()) {
1027
1056
continue ;
1028
1057
}
1029
1058
int idx = part .indexOf ("=" );
@@ -1034,7 +1063,10 @@ private Map<String, List<String>> parseOptions(final String optionsPart) {
1034
1063
if (valueList == null ) {
1035
1064
valueList = new ArrayList <>(1 );
1036
1065
}
1037
- valueList .add (urldecode (value ));
1066
+ if (decodeWholeOptionValue (isOidc , key )) {
1067
+ value = urldecode (value );
1068
+ }
1069
+ valueList .add (value );
1038
1070
optionsMap .put (key , valueList );
1039
1071
} else {
1040
1072
throw new IllegalArgumentException (format ("The connection string contains an invalid option '%s'. "
0 commit comments