Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a5e367f

Browse files
katcharovstIncMale
andcommittedMar 4, 2024
Add Human OIDC Workflow (#1316)
* Add human workflow * Apply suggestions from code review Co-authored-by: Valentin Kovalenko <[email protected]> * Add expiresIn, address PR comments * PR fixes * Fix compilation --------- Co-authored-by: Valentin Kovalenko <[email protected]>
1 parent d3bd88f commit a5e367f

File tree

6 files changed

+845
-78
lines changed

6 files changed

+845
-78
lines changed
 

‎driver-core/src/main/com/mongodb/ConnectionString.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@
4747
import java.util.Objects;
4848
import java.util.Set;
4949
import java.util.concurrent.TimeUnit;
50+
import java.util.stream.Collectors;
51+
import java.util.stream.Stream;
5052

53+
import static com.mongodb.MongoCredential.ALLOWED_HOSTS_KEY;
5154
import static com.mongodb.internal.connection.OidcAuthenticator.OidcValidator.validateCreateOidcCredential;
5255
import static java.lang.String.format;
5356
import static java.util.Arrays.asList;
@@ -282,6 +285,9 @@ public class ConnectionString {
282285
private static final Set<String> ALLOWED_OPTIONS_IN_TXT_RECORD =
283286
new HashSet<>(asList("authsource", "replicaset", "loadbalanced"));
284287
private static final Logger LOGGER = Loggers.getLogger("uri");
288+
private static final List<String> MECHANISM_KEYS_DISALLOWED_IN_CONNECTION_STRING = Stream.of(ALLOWED_HOSTS_KEY)
289+
.map(k -> k.toLowerCase())
290+
.collect(Collectors.toList());
285291

286292
private final MongoCredential credential;
287293
private final boolean isSrvProtocol;
@@ -917,6 +923,11 @@ private MongoCredential createCredentials(final Map<String, List<String>> option
917923
}
918924
String key = mechanismPropertyKeyValue[0].trim().toLowerCase();
919925
String value = mechanismPropertyKeyValue[1].trim();
926+
if (MECHANISM_KEYS_DISALLOWED_IN_CONNECTION_STRING.contains(key)) {
927+
throw new IllegalArgumentException(format("The connection string contains disallowed mechanism properties. "
928+
+ "'%s' must be set on the credential programmatically.", key));
929+
}
930+
920931
if (key.equals("canonicalize_host_name")) {
921932
credential = credential.withMechanismProperty(key, Boolean.valueOf(value));
922933
} else {

‎driver-core/src/main/com/mongodb/MongoCredential.java

Lines changed: 125 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Arrays;
2626
import java.util.Collections;
2727
import java.util.HashMap;
28+
import java.util.List;
2829
import java.util.Map;
2930
import java.util.Objects;
3031

@@ -187,7 +188,8 @@ public final class MongoCredential {
187188
* The provider name. The value must be a string.
188189
* <p>
189190
* If this is provided,
190-
* {@link MongoCredential#OIDC_CALLBACK_KEY}
191+
* {@link MongoCredential#OIDC_CALLBACK_KEY} and
192+
* {@link MongoCredential#OIDC_HUMAN_CALLBACK_KEY}
191193
* must not be provided.
192194
*
193195
* @see #createOidcCredential(String)
@@ -197,17 +199,60 @@ public final class MongoCredential {
197199

198200
/**
199201
* This callback is invoked when the OIDC-based authenticator requests
200-
* tokens from the identity provider. The type of the value must be
201-
* {@link OidcRequestCallback}.
202+
* a token. The type of the value must be {@link OidcCallback}.
203+
* {@link IdpInfo} will not be supplied to the callback,
204+
* and a {@linkplain OidcCallbackResult#getRefreshToken() refresh token}
205+
* must not be returned by the callback.
202206
* <p>
203207
* If this is provided, {@link MongoCredential#PROVIDER_NAME_KEY}
208+
* and {@link MongoCredential#OIDC_HUMAN_CALLBACK_KEY}
204209
* must not be provided.
205210
*
206211
* @see #createOidcCredential(String)
207212
* @since 4.10
208213
*/
209214
public static final String OIDC_CALLBACK_KEY = "OIDC_CALLBACK";
210215

216+
/**
217+
* This callback is invoked when the OIDC-based authenticator requests
218+
* a token from the identity provider (IDP) using the IDP information
219+
* from the MongoDB server. The type of the value must be
220+
* {@link OidcCallback}.
221+
* <p>
222+
* If this is provided, {@link MongoCredential#PROVIDER_NAME_KEY}
223+
* and {@link MongoCredential#OIDC_CALLBACK_KEY}
224+
* must not be provided.
225+
*
226+
* @see #createOidcCredential(String)
227+
* @since 4.10
228+
*/
229+
public static final String OIDC_HUMAN_CALLBACK_KEY = "OIDC_HUMAN_CALLBACK";
230+
231+
232+
/**
233+
* Mechanism key for a list of allowed hostnames or ip-addresses for MongoDB connections. Ports must be excluded.
234+
* The hostnames may include a leading "*." wildcard, which allows for matching (potentially nested) subdomains.
235+
* When MONGODB-OIDC authentication is attempted against a hostname that does not match any of list of allowed hosts
236+
* the driver will raise an error. The type of the value must be {@code List<String>}.
237+
*
238+
* @see MongoCredential#DEFAULT_ALLOWED_HOSTS
239+
* @see #createOidcCredential(String)
240+
* @since 4.10
241+
*/
242+
public static final String ALLOWED_HOSTS_KEY = "ALLOWED_HOSTS";
243+
244+
/**
245+
* The list of allowed hosts that will be used if no
246+
* {@link MongoCredential#ALLOWED_HOSTS_KEY} value is supplied.
247+
* The default allowed hosts are:
248+
* {@code "*.mongodb.net", "*.mongodb-qa.net", "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"}
249+
*
250+
* @see #createOidcCredential(String)
251+
* @since 4.10
252+
*/
253+
public static final List<String> DEFAULT_ALLOWED_HOSTS = Collections.unmodifiableList(Arrays.asList(
254+
"*.mongodb.net", "*.mongodb-qa.net", "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"));
255+
211256
/**
212257
* Creates a MongoCredential instance with an unspecified mechanism. The client will negotiate the best mechanism based on the
213258
* version of the server that the client is authenticating to.
@@ -365,6 +410,8 @@ public static MongoCredential createAwsCredential(@Nullable final String userNam
365410
* @see #withMechanismProperty(String, Object)
366411
* @see #PROVIDER_NAME_KEY
367412
* @see #OIDC_CALLBACK_KEY
413+
* @see #OIDC_HUMAN_CALLBACK_KEY
414+
* @see #ALLOWED_HOSTS_KEY
368415
* @mongodb.server.release 7.0
369416
*/
370417
public static MongoCredential createOidcCredential(@Nullable final String userName) {
@@ -593,10 +640,15 @@ public String toString() {
593640
}
594641

595642
/**
596-
* The context for the {@link OidcRequestCallback#onRequest(OidcRequestContext) OIDC request callback}.
643+
* The context for the {@link OidcCallback#onRequest(OidcCallbackContext) OIDC request callback}.
597644
*/
598645
@Evolving
599-
public interface OidcRequestContext {
646+
public interface OidcCallbackContext {
647+
/**
648+
* @return The OIDC Identity Provider's configuration that can be used to acquire an Access Token.
649+
*/
650+
@Nullable
651+
IdpInfo getIdpInfo();
600652

601653
/**
602654
* @return The timeout that this callback must complete within.
@@ -607,6 +659,12 @@ public interface OidcRequestContext {
607659
* @return The OIDC callback API version. Currently, version 1.
608660
*/
609661
int getVersion();
662+
663+
/**
664+
* @return The OIDC Refresh token supplied by a prior callback invocation.
665+
*/
666+
@Nullable
667+
String getRefreshToken();
610668
}
611669

612670
/**
@@ -616,27 +674,76 @@ public interface OidcRequestContext {
616674
* It does not have to be thread-safe, unless it is provided to multiple
617675
* MongoClients.
618676
*/
619-
public interface OidcRequestCallback {
677+
public interface OidcCallback {
620678
/**
621679
* @param context The context.
622680
* @return The response produced by an OIDC Identity Provider
623681
*/
624-
RequestCallbackResult onRequest(OidcRequestContext context);
682+
OidcCallbackResult onRequest(OidcCallbackContext context);
683+
}
684+
685+
/**
686+
* The OIDC Identity Provider's configuration that can be used to acquire an Access Token.
687+
*/
688+
@Evolving
689+
public interface IdpInfo {
690+
/**
691+
* @return URL which describes the Authorization Server. This identifier is the
692+
* iss of provided access tokens, and is viable for RFC8414 metadata
693+
* discovery and RFC9207 identification.
694+
*/
695+
String getIssuer();
696+
697+
/**
698+
* @return Unique client ID for this OIDC client.
699+
*/
700+
String getClientId();
701+
702+
/**
703+
* @return Additional scopes to request from Identity Provider. Immutable.
704+
*/
705+
List<String> getRequestScopes();
625706
}
626707

627708
/**
628709
* The response produced by an OIDC Identity Provider.
629710
*/
630-
public static final class RequestCallbackResult {
711+
public static final class OidcCallbackResult {
631712

632713
private final String accessToken;
633714

715+
private final Duration expiresIn;
716+
717+
@Nullable
718+
private final String refreshToken;
719+
720+
/**
721+
* @param accessToken The OIDC access token.
722+
* @param expiresIn Time until the access token expires.
723+
* A {@linkplain Duration#isZero() zero-length} duration
724+
* means that the access token does not expire.
725+
*/
726+
public OidcCallbackResult(final String accessToken, final Duration expiresIn) {
727+
this(accessToken, expiresIn, null);
728+
}
729+
634730
/**
635-
* @param accessToken The OIDC access token
731+
* @param accessToken The OIDC access token.
732+
* @param expiresIn Time until the access token expires.
733+
* A {@linkplain Duration#isZero() zero-length} duration
734+
* means that the access token does not expire.
735+
* @param refreshToken The refresh token. If null, refresh will not be attempted.
636736
*/
637-
public RequestCallbackResult(final String accessToken) {
737+
public OidcCallbackResult(final String accessToken, final Duration expiresIn,
738+
@Nullable final String refreshToken) {
638739
notNull("accessToken", accessToken);
740+
notNull("expiresIn", expiresIn);
741+
if (expiresIn.isNegative()) {
742+
throw new IllegalArgumentException("expiresIn must not be a negative value");
743+
}
639744
this.accessToken = accessToken;
745+
this.expiresIn = expiresIn;
746+
this.refreshToken = refreshToken;
640747
}
641748

642749
/**
@@ -645,5 +752,13 @@ public RequestCallbackResult(final String accessToken) {
645752
public String getAccessToken() {
646753
return accessToken;
647754
}
755+
756+
/**
757+
* @return The OIDC refresh token. If null, refresh will not be attempted.
758+
*/
759+
@Nullable
760+
public String getRefreshToken() {
761+
return refreshToken;
762+
}
648763
}
649764
}

‎driver-core/src/main/com/mongodb/internal/connection/OidcAuthenticator.java

Lines changed: 274 additions & 38 deletions
Large diffs are not rendered by default.

‎driver-core/src/test/unit/com/mongodb/AuthConnectionStringTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private MongoCredential getMongoCredential() {
119119
if ("oidcRequest".equals(string)) {
120120
credential = credential.withMechanismProperty(
121121
OIDC_CALLBACK_KEY,
122-
(MongoCredential.OidcRequestCallback) (context) -> null);
122+
(MongoCredential.OidcCallback) (context) -> null);
123123
} else {
124124
fail("Unsupported callback: " + string);
125125
}
@@ -176,7 +176,7 @@ private void assertMechanismProperties(final MongoCredential credential) {
176176
} else if ((document.get(key).isBoolean())) {
177177
boolean expectedValue = document.getBoolean(key).getValue();
178178
if (OIDC_CALLBACK_KEY.equals(key)) {
179-
assertTrue(actualMechanismProperty instanceof MongoCredential.OidcRequestCallback);
179+
assertTrue(actualMechanismProperty instanceof MongoCredential.OidcCallback);
180180
return;
181181
}
182182
assertNotNull(actualMechanismProperty);

‎driver-sync/src/test/functional/com/mongodb/client/unified/Entities.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@
7373
import org.bson.BsonDouble;
7474
import org.bson.BsonInt32;
7575
import org.bson.BsonInt64;
76-
import org.bson.BsonNumber;
7776
import org.bson.BsonString;
7877
import org.bson.BsonValue;
7978

@@ -82,6 +81,7 @@
8281
import java.nio.file.Files;
8382
import java.nio.file.Path;
8483
import java.nio.file.Paths;
84+
import java.time.Duration;
8585
import java.util.ArrayList;
8686
import java.util.HashMap;
8787
import java.util.HashSet;
@@ -542,15 +542,15 @@ private void initClient(final BsonDocument entity, final String id,
542542
if (isOidc && hasPlaceholder) {
543543
clientSettingsBuilder.credential(credential.withMechanismProperty(
544544
MongoCredential.OIDC_CALLBACK_KEY,
545-
(MongoCredential.OidcRequestCallback) context -> {
545+
(MongoCredential.OidcCallback) context -> {
546546
Path path = Paths.get(getenv(OidcAuthenticator.AWS_WEB_IDENTITY_TOKEN_FILE));
547547
String accessToken;
548548
try {
549549
accessToken = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
550550
} catch (IOException e) {
551551
throw new RuntimeException(e);
552552
}
553-
return new MongoCredential.RequestCallbackResult(accessToken);
553+
return new MongoCredential.OidcCallbackResult(accessToken, Duration.ZERO);
554554
}));
555555
break;
556556
}

‎driver-sync/src/test/functional/com/mongodb/internal/connection/OidcAuthenticationProseTests.java

Lines changed: 430 additions & 25 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.