Skip to content

Commit

Permalink
Cleanup, update since annotations, updates to match spec API
Browse files Browse the repository at this point in the history
  • Loading branch information
katcharov committed Apr 23, 2024
1 parent 0f0a5ef commit b277316
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 56 deletions.
53 changes: 38 additions & 15 deletions driver-core/src/main/com/mongodb/MongoCredential.java
Original file line number Diff line number Diff line change
Expand Up @@ -194,23 +194,23 @@ public final class MongoCredential {
* must not be provided.
*
* @see #createOidcCredential(String)
* @since 4.10
* @since 5.1
*/
public static final String ENVIRONMENT_KEY = "ENVIRONMENT";

/**
* This callback is invoked when the OIDC-based authenticator requests
* 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}
* and a {@linkplain com.mongodb.MongoCredential.OidcTokens#getRefreshToken() refresh token}
* must not be returned by the callback.
* <p>
* If this is provided, {@link MongoCredential#ENVIRONMENT_KEY}
* and {@link MongoCredential#OIDC_HUMAN_CALLBACK_KEY}
* must not be provided.
*
* @see #createOidcCredential(String)
* @since 4.10
* @since 5.1
*/
public static final String OIDC_CALLBACK_KEY = "OIDC_CALLBACK";

Expand All @@ -225,7 +225,7 @@ public final class MongoCredential {
* must not be provided.
*
* @see #createOidcCredential(String)
* @since 4.10
* @since 5.1
*/
public static final String OIDC_HUMAN_CALLBACK_KEY = "OIDC_HUMAN_CALLBACK";

Expand All @@ -238,7 +238,7 @@ public final class MongoCredential {
*
* @see MongoCredential#DEFAULT_ALLOWED_HOSTS
* @see #createOidcCredential(String)
* @since 4.10
* @since 5.1
*/
public static final String ALLOWED_HOSTS_KEY = "ALLOWED_HOSTS";

Expand All @@ -249,15 +249,15 @@ public final class MongoCredential {
* {@code "*.mongodb.net", "*.mongodb-qa.net", "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"}
*
* @see #createOidcCredential(String)
* @since 4.10
* @since 5.1
*/
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"));

/**
* The token resource.
*
* @since TODO-OIDC update all
* @since 5.1
*/
public static final String TOKEN_RESOURCE_KEY = "TOKEN_RESOURCE";

Expand Down Expand Up @@ -414,7 +414,7 @@ public static MongoCredential createAwsCredential(@Nullable final String userNam
*
* @param userName the user name, which may be null. This is the OIDC principal name.
* @return the credential
* @since 4.10
* @since 5.1
* @see #withMechanismProperty(String, Object)
* @see #ENVIRONMENT_KEY
* @see #OIDC_CALLBACK_KEY
Expand Down Expand Up @@ -650,14 +650,16 @@ public String toString() {

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

/**
* @return The timeout that this callback must complete within.
Expand All @@ -669,6 +671,12 @@ public interface OidcCallbackContext {
*/
int getVersion();

/**
* @return The OIDC Identity Provider's configuration that can be used to acquire an Access Token.
*/
@Nullable
IdpInfo getIdpInfo();

/**
* @return The OIDC Refresh token supplied by a prior callback invocation.
*/
Expand All @@ -682,17 +690,21 @@ public interface OidcCallbackContext {
* <p>
* It does not have to be thread-safe, unless it is provided to multiple
* MongoClients.
*
* @since 5.1
*/
public interface OidcCallback {
/**
* @param context The context.
* @return The response produced by an OIDC Identity Provider
*/
OidcCallbackResult onRequest(OidcCallbackContext context);
OidcTokens onRequest(OidcCallbackContext context);
}

/**
* The OIDC Identity Provider's configuration that can be used to acquire an Access Token.
*
* @since 5.1
*/
@Evolving
public interface IdpInfo {
Expand All @@ -716,9 +728,11 @@ public interface IdpInfo {
}

/**
* The response produced by an OIDC Identity Provider.
* The OIDC credential information.
*
* @since 5.1
*/
public static final class OidcCallbackResult {
public static final class OidcTokens {

private final String accessToken;

Expand All @@ -727,13 +741,22 @@ public static final class OidcCallbackResult {
@Nullable
private final String refreshToken;


/**
* An access token that does not expire.
* @param accessToken The OIDC access token.
*/
public OidcTokens(final String accessToken) {
this(accessToken, Duration.ZERO, null);
}

/**
* @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) {
public OidcTokens(final String accessToken, final Duration expiresIn) {
this(accessToken, expiresIn, null);
}

Expand All @@ -744,7 +767,7 @@ public OidcCallbackResult(final String accessToken, final Duration expiresIn) {
* means that the access token does not expire.
* @param refreshToken The refresh token. If null, refresh will not be attempted.
*/
public OidcCallbackResult(final String accessToken, final Duration expiresIn,
public OidcTokens(final String accessToken, final Duration expiresIn,
@Nullable final String refreshToken) {
notNull("accessToken", accessToken);
notNull("expiresIn", expiresIn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.mongodb.MongoCommandException;
import com.mongodb.MongoConfigurationException;
import com.mongodb.MongoCredential;
import com.mongodb.MongoCredential.OidcCallbackResult;
import com.mongodb.MongoCredential.OidcTokens;
import com.mongodb.MongoException;
import com.mongodb.MongoSecurityException;
import com.mongodb.ServerAddress;
Expand Down Expand Up @@ -192,7 +192,7 @@ private OidcCallback getRequestCallback() {
public static OidcCallback getTestCallback() {
return (context) -> {
String accessToken = readTestTokenFromFile();
return new OidcCallbackResult(accessToken, Duration.ZERO);
return new OidcTokens(accessToken);
};
}

Expand All @@ -202,7 +202,7 @@ public static OidcCallback getAzureCallback(final MongoCredential credential) {
String resource = assertNotNull(credential.getMechanismProperty(TOKEN_RESOURCE_KEY, null));
String objectId = credential.getUserName();
CredentialInfo response = AzureCredentialHelper.fetchAzureCredentialInfo(resource, objectId);
return new OidcCallbackResult(response.getAccessToken(), response.getExpiresIn());
return new OidcTokens(response.getAccessToken(), response.getExpiresIn());
};
}

Expand All @@ -211,7 +211,7 @@ public static OidcCallback getGcpCallback(final MongoCredential credential) {
return (context) -> {
String resource = assertNotNull(credential.getMechanismProperty(TOKEN_RESOURCE_KEY, null));
CredentialInfo response = GcpCredentialHelper.fetchGcpCredentialInfo(resource);
return new OidcCallbackResult(response.getAccessToken(), response.getExpiresIn());
return new OidcTokens(response.getAccessToken(), response.getExpiresIn());
};
}

Expand Down Expand Up @@ -289,6 +289,7 @@ private byte[] evaluate(final byte[] challenge) {
String cachedAccessToken = validatedCachedAccessToken();
OidcCallback requestCallback = getRequestCallback();
boolean isHuman = isHumanCallback();
String userName = getMongoCredentialWithCache().getCredential().getUserName();

if (cachedAccessToken != null) {
fallbackState = FallbackState.PHASE_1_CACHED_TOKEN;
Expand All @@ -299,17 +300,17 @@ private byte[] evaluate(final byte[] challenge) {
assertNotNull(cachedIdpInfo);
// Invoke Callback using cached Refresh Token
fallbackState = FallbackState.PHASE_2_REFRESH_CALLBACK_TOKEN;
OidcCallbackResult result = requestCallback.onRequest(new OidcCallbackContextImpl(
CALLBACK_TIMEOUT, cachedIdpInfo, cachedRefreshToken));
OidcTokens result = requestCallback.onRequest(new OidcCallbackContextImpl(
CALLBACK_TIMEOUT, cachedIdpInfo, cachedRefreshToken, userName));
jwt[0] = populateCacheWithCallbackResultAndPrepareJwt(cachedIdpInfo, result);
} else {
// cache is empty

if (!isHuman) {
// no principal request
fallbackState = FallbackState.PHASE_3B_CALLBACK_TOKEN;
OidcCallbackResult result = requestCallback.onRequest(new OidcCallbackContextImpl(
CALLBACK_TIMEOUT));
OidcTokens result = requestCallback.onRequest(new OidcCallbackContextImpl(
CALLBACK_TIMEOUT, userName));
jwt[0] = populateCacheWithCallbackResultAndPrepareJwt(null, result);
if (result.getRefreshToken() != null) {
throw new MongoConfigurationException(
Expand All @@ -333,13 +334,13 @@ private byte[] evaluate(final byte[] challenge) {
if (!alreadyTriedPrincipal && idpInfoNotPresent) {
// request for idp info, only in the human workflow
fallbackState = FallbackState.PHASE_3A_PRINCIPAL;
jwt[0] = prepareUsername(getMongoCredentialWithCache().getCredential().getUserName());
jwt[0] = prepareUsername(userName);
} else {
IdpInfo idpInfo = toIdpInfo(challenge);
// there is no cached refresh token
fallbackState = FallbackState.PHASE_3B_CALLBACK_TOKEN;
OidcCallbackResult result = requestCallback.onRequest(new OidcCallbackContextImpl(
CALLBACK_TIMEOUT, idpInfo, null));
OidcTokens result = requestCallback.onRequest(new OidcCallbackContextImpl(
CALLBACK_TIMEOUT, idpInfo, null, userName));
jwt[0] = populateCacheWithCallbackResultAndPrepareJwt(idpInfo, result);
}
}
Expand Down Expand Up @@ -499,14 +500,14 @@ private static String readTestTokenFromFile() {

private byte[] populateCacheWithCallbackResultAndPrepareJwt(
@Nullable final IdpInfo serverInfo,
@Nullable final OidcCallbackResult oidcCallbackResult) {
if (oidcCallbackResult == null) {
@Nullable final OidcTokens oidcTokens) {
if (oidcTokens == null) {
throw new MongoConfigurationException("Result of callback must not be null");
}
OidcCacheEntry newEntry = new OidcCacheEntry(oidcCallbackResult.getAccessToken(),
oidcCallbackResult.getRefreshToken(), serverInfo);
OidcCacheEntry newEntry = new OidcCacheEntry(oidcTokens.getAccessToken(),
oidcTokens.getRefreshToken(), serverInfo);
getMongoCredentialWithCache().setOidcCacheEntry(newEntry);
return prepareTokenAsJwt(oidcCallbackResult.getAccessToken());
return prepareTokenAsJwt(oidcTokens.getAccessToken());
}

private static byte[] prepareUsername(@Nullable final String username) {
Expand Down Expand Up @@ -663,20 +664,26 @@ static class OidcCallbackContextImpl implements OidcCallbackContext {
private final IdpInfo idpInfo;
@Nullable
private final String refreshToken;
@Nullable
private final String userName;

OidcCallbackContextImpl(final Duration timeout) {
OidcCallbackContextImpl(final Duration timeout, @Nullable final String userName) {
this.timeout = assertNotNull(timeout);
this.idpInfo = null;
this.refreshToken = null;
this.userName = userName;
}

OidcCallbackContextImpl(final Duration timeout, final IdpInfo idpInfo, @Nullable final String refreshToken) {
OidcCallbackContextImpl(final Duration timeout, final IdpInfo idpInfo,
@Nullable final String refreshToken, @Nullable final String userName) {
this.timeout = assertNotNull(timeout);
this.idpInfo = assertNotNull(idpInfo);
this.refreshToken = refreshToken;
this.userName = userName;
}

@Override
@Nullable
public IdpInfo getIdpInfo() {
return idpInfo;
}
Expand All @@ -692,9 +699,16 @@ public int getVersion() {
}

@Override
@Nullable
public String getRefreshToken() {
return refreshToken;
}

@Override
@Nullable
public String getUserName() {
return userName;
}
}

@VisibleForTesting(otherwise = VisibleForTesting.AccessModifier.PRIVATE)
Expand Down

0 comments on commit b277316

Please sign in to comment.