Skip to content

Add Human OIDC Workflow #1316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 4, 2024
Merged
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
11 changes: 11 additions & 0 deletions driver-core/src/main/com/mongodb/ConnectionString.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.mongodb.MongoCredential.ALLOWED_HOSTS_KEY;
import static com.mongodb.internal.connection.OidcAuthenticator.OidcValidator.validateCreateOidcCredential;
import static java.lang.String.format;
import static java.util.Arrays.asList;
Expand Down Expand Up @@ -272,6 +275,9 @@ public class ConnectionString {
private static final Set<String> ALLOWED_OPTIONS_IN_TXT_RECORD =
new HashSet<>(asList("authsource", "replicaset", "loadbalanced"));
private static final Logger LOGGER = Loggers.getLogger("uri");
private static final List<String> MECHANISM_KEYS_DISALLOWED_IN_CONNECTION_STRING = Stream.of(ALLOWED_HOSTS_KEY)
.map(k -> k.toLowerCase())
.collect(Collectors.toList());

private final MongoCredential credential;
private final boolean isSrvProtocol;
Expand Down Expand Up @@ -902,6 +908,11 @@ private MongoCredential createCredentials(final Map<String, List<String>> option
}
String key = mechanismPropertyKeyValue[0].trim().toLowerCase();
String value = mechanismPropertyKeyValue[1].trim();
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));
}

if (key.equals("canonicalize_host_name")) {
credential = credential.withMechanismProperty(key, Boolean.valueOf(value));
} else {
Expand Down
135 changes: 125 additions & 10 deletions driver-core/src/main/com/mongodb/MongoCredential.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -187,7 +188,8 @@ public final class MongoCredential {
* The provider name. The value must be a string.
* <p>
* If this is provided,
* {@link MongoCredential#OIDC_CALLBACK_KEY}
* {@link MongoCredential#OIDC_CALLBACK_KEY} and
* {@link MongoCredential#OIDC_HUMAN_CALLBACK_KEY}
* must not be provided.
*
* @see #createOidcCredential(String)
Expand All @@ -197,17 +199,60 @@ public final class MongoCredential {

/**
* This callback is invoked when the OIDC-based authenticator requests
* tokens from the identity provider. The type of the value must be
* {@link OidcRequestCallback}.
* a token. The type of the value must be {@link OidcCallback}.
* {@link IdpInfo} will not be supplied to the callback,
* and a {@linkplain OidcCallbackResult#getRefreshToken() refresh token}
* must not be returned by the callback.
* <p>
* If this is provided, {@link MongoCredential#PROVIDER_NAME_KEY}
* and {@link MongoCredential#OIDC_HUMAN_CALLBACK_KEY}
* must not be provided.
*
* @see #createOidcCredential(String)
* @since 4.10
*/
public static final String OIDC_CALLBACK_KEY = "OIDC_CALLBACK";

/**
* This callback is invoked when the OIDC-based authenticator requests
* a token from the identity provider (IDP) using the IDP information
* from the MongoDB server. The type of the value must be
* {@link OidcCallback}.
* <p>
* If this is provided, {@link MongoCredential#PROVIDER_NAME_KEY}
* and {@link MongoCredential#OIDC_CALLBACK_KEY}
* must not be provided.
*
* @see #createOidcCredential(String)
* @since 4.10
*/
public static final String OIDC_HUMAN_CALLBACK_KEY = "OIDC_HUMAN_CALLBACK";


/**
* Mechanism key for a list of allowed hostnames or ip-addresses for MongoDB connections. Ports must be excluded.
* The hostnames may include a leading "*." wildcard, which allows for matching (potentially nested) subdomains.
* When MONGODB-OIDC authentication is attempted against a hostname that does not match any of list of allowed hosts
* the driver will raise an error. The type of the value must be {@code List<String>}.
*
* @see MongoCredential#DEFAULT_ALLOWED_HOSTS
* @see #createOidcCredential(String)
* @since 4.10
*/
public static final String ALLOWED_HOSTS_KEY = "ALLOWED_HOSTS";

/**
* The list of allowed hosts that will be used if no
* {@link MongoCredential#ALLOWED_HOSTS_KEY} value is supplied.
* The default allowed hosts are:
* {@code "*.mongodb.net", "*.mongodb-qa.net", "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"}
*
* @see #createOidcCredential(String)
* @since 4.10
*/
public static final List<String> DEFAULT_ALLOWED_HOSTS = Collections.unmodifiableList(Arrays.asList(
"*.mongodb.net", "*.mongodb-qa.net", "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"));

/**
* Creates a MongoCredential instance with an unspecified mechanism. The client will negotiate the best mechanism based on the
* version of the server that the client is authenticating to.
Expand Down Expand Up @@ -365,6 +410,8 @@ public static MongoCredential createAwsCredential(@Nullable final String userNam
* @see #withMechanismProperty(String, Object)
* @see #PROVIDER_NAME_KEY
* @see #OIDC_CALLBACK_KEY
* @see #OIDC_HUMAN_CALLBACK_KEY
* @see #ALLOWED_HOSTS_KEY
* @mongodb.server.release 7.0
*/
public static MongoCredential createOidcCredential(@Nullable final String userName) {
Expand Down Expand Up @@ -593,10 +640,15 @@ public String toString() {
}

/**
* The context for the {@link OidcRequestCallback#onRequest(OidcRequestContext) OIDC request callback}.
* The context for the {@link OidcCallback#onRequest(OidcCallbackContext) OIDC request callback}.
*/
@Evolving
public interface OidcRequestContext {
public interface OidcCallbackContext {
/**
* @return The OIDC Identity Provider's configuration that can be used to acquire an Access Token.
*/
@Nullable
IdpInfo getIdpInfo();

/**
* @return The timeout that this callback must complete within.
Expand All @@ -607,6 +659,12 @@ public interface OidcRequestContext {
* @return The OIDC callback API version. Currently, version 1.
*/
int getVersion();

/**
* @return The OIDC Refresh token supplied by a prior callback invocation.
*/
@Nullable
String getRefreshToken();
}

/**
Expand All @@ -616,27 +674,76 @@ public interface OidcRequestContext {
* It does not have to be thread-safe, unless it is provided to multiple
* MongoClients.
*/
public interface OidcRequestCallback {
public interface OidcCallback {
/**
* @param context The context.
* @return The response produced by an OIDC Identity Provider
*/
RequestCallbackResult onRequest(OidcRequestContext context);
OidcCallbackResult onRequest(OidcCallbackContext context);
}

/**
* The OIDC Identity Provider's configuration that can be used to acquire an Access Token.
*/
@Evolving
public interface IdpInfo {
/**
* @return URL which describes the Authorization Server. This identifier is the
* iss of provided access tokens, and is viable for RFC8414 metadata
* discovery and RFC9207 identification.
*/
String getIssuer();

/**
* @return Unique client ID for this OIDC client.
*/
String getClientId();

/**
* @return Additional scopes to request from Identity Provider. Immutable.
*/
List<String> getRequestScopes();
}

/**
* The response produced by an OIDC Identity Provider.
*/
public static final class RequestCallbackResult {
public static final class OidcCallbackResult {

private final String accessToken;

private final Duration expiresIn;

@Nullable
private final String refreshToken;

/**
* @param accessToken The OIDC access token.
* @param expiresIn Time until the access token expires.
* A {@linkplain Duration#isZero() zero-length} duration
* means that the access token does not expire.
*/
public OidcCallbackResult(final String accessToken, final Duration expiresIn) {
this(accessToken, expiresIn, null);
}

/**
* @param accessToken The OIDC access token
* @param accessToken The OIDC access token.
* @param expiresIn Time until the access token expires.
* A {@linkplain Duration#isZero() zero-length} duration
* means that the access token does not expire.
* @param refreshToken The refresh token. If null, refresh will not be attempted.
*/
public RequestCallbackResult(final String accessToken) {
public OidcCallbackResult(final String accessToken, final Duration expiresIn,
@Nullable final String refreshToken) {
notNull("accessToken", accessToken);
notNull("expiresIn", expiresIn);
if (expiresIn.isNegative()) {
throw new IllegalArgumentException("expiresIn must not be a negative value");
}
this.accessToken = accessToken;
this.expiresIn = expiresIn;
this.refreshToken = refreshToken;
}

/**
Expand All @@ -645,5 +752,13 @@ public RequestCallbackResult(final String accessToken) {
public String getAccessToken() {
return accessToken;
}

/**
* @return The OIDC refresh token. If null, refresh will not be attempted.
*/
@Nullable
public String getRefreshToken() {
return refreshToken;
}
}
}
Loading