|
19 | 19 | import com.mongodb.annotations.Beta;
|
20 | 20 | import com.mongodb.annotations.Immutable;
|
21 | 21 | import com.mongodb.lang.Nullable;
|
| 22 | +import org.bson.BsonDocument; |
| 23 | +import org.bson.BsonString; |
| 24 | +import org.bson.conversions.Bson; |
22 | 25 |
|
23 | 26 | import java.util.Arrays;
|
24 | 27 | import java.util.Collections;
|
25 | 28 | import java.util.HashMap;
|
| 29 | +import java.util.List; |
26 | 30 | import java.util.Map;
|
27 | 31 | import java.util.Objects;
|
| 32 | +import java.util.stream.Collectors; |
28 | 33 |
|
29 | 34 | import static com.mongodb.AuthenticationMechanism.GSSAPI;
|
30 | 35 | import static com.mongodb.AuthenticationMechanism.MONGODB_AWS;
|
| 36 | +import static com.mongodb.AuthenticationMechanism.MONGODB_OIDC; |
31 | 37 | import static com.mongodb.AuthenticationMechanism.MONGODB_X509;
|
32 | 38 | import static com.mongodb.AuthenticationMechanism.PLAIN;
|
33 | 39 | import static com.mongodb.AuthenticationMechanism.SCRAM_SHA_1;
|
34 | 40 | import static com.mongodb.AuthenticationMechanism.SCRAM_SHA_256;
|
35 | 41 | import static com.mongodb.assertions.Assertions.notNull;
|
| 42 | +import static com.mongodb.internal.connection.OidcAuthenticator.OidcValidator.validateOidcCredentialConstruction; |
36 | 43 |
|
37 | 44 | /**
|
38 | 45 | * Represents credentials to authenticate to a mongo server,as well as the source of the credentials and the authentication mechanism to
|
@@ -179,6 +186,21 @@ public final class MongoCredential {
|
179 | 186 | @Beta(Beta.Reason.CLIENT)
|
180 | 187 | public static final String AWS_CREDENTIAL_PROVIDER_KEY = "AWS_CREDENTIAL_PROVIDER";
|
181 | 188 |
|
| 189 | + /** |
| 190 | + * |
| 191 | + */ |
| 192 | + public static final String PROVIDER_NAME = "PROVIDER_NAME"; |
| 193 | + |
| 194 | + /** |
| 195 | + * |
| 196 | + */ |
| 197 | + public static final String REQUEST_TOKEN_CALLBACK = "REQUEST_TOKEN_CALLBACK"; |
| 198 | + |
| 199 | + /** |
| 200 | + * |
| 201 | + */ |
| 202 | + public static final String REFRESH_TOKEN_CALLBACK = "REFRESH_TOKEN_CALLBACK"; |
| 203 | + |
182 | 204 | /**
|
183 | 205 | * Creates a MongoCredential instance with an unspecified mechanism. The client will negotiate the best mechanism based on the
|
184 | 206 | * version of the server that the client is authenticating to.
|
@@ -327,6 +349,45 @@ public static MongoCredential createAwsCredential(@Nullable final String userNam
|
327 | 349 | return new MongoCredential(MONGODB_AWS, userName, "$external", password);
|
328 | 350 | }
|
329 | 351 |
|
| 352 | + /** |
| 353 | + * |
| 354 | + * @param userName |
| 355 | + * @param request |
| 356 | + * @param refresh |
| 357 | + * @return |
| 358 | + */ |
| 359 | + public static MongoCredential createOidcCredential( |
| 360 | + @Nullable final String userName, |
| 361 | + final RequestCallback request, |
| 362 | + @Nullable final RefreshCallback refresh) { |
| 363 | + MongoCredential mongoCredential = createOidcCredentialInternal(userName) |
| 364 | + .withMechanismProperty(REQUEST_TOKEN_CALLBACK, request); |
| 365 | + if (refresh != null) { |
| 366 | + mongoCredential = mongoCredential |
| 367 | + .withMechanismProperty(REFRESH_TOKEN_CALLBACK, refresh); |
| 368 | + } |
| 369 | + return mongoCredential; |
| 370 | + } |
| 371 | + |
| 372 | + /** |
| 373 | + * |
| 374 | + * @param providerName |
| 375 | + * @return |
| 376 | + */ |
| 377 | + public static MongoCredential createOidcCredential(final String providerName) { |
| 378 | + return createOidcCredentialInternal(null) |
| 379 | + .withMechanismProperty(PROVIDER_NAME, providerName); |
| 380 | + } |
| 381 | + |
| 382 | + /** |
| 383 | + * |
| 384 | + * @param userName |
| 385 | + * @return |
| 386 | + */ |
| 387 | + static MongoCredential createOidcCredentialInternal(@Nullable final String userName) { |
| 388 | + return new MongoCredential(MONGODB_OIDC, userName, "$external", null); |
| 389 | + } |
| 390 | + |
330 | 391 | /**
|
331 | 392 | * Creates a new MongoCredential as a copy of this instance, with the specified mechanism property added.
|
332 | 393 | *
|
@@ -377,7 +438,11 @@ public MongoCredential withMechanism(final AuthenticationMechanism mechanism) {
|
377 | 438 | @Nullable final char[] password,
|
378 | 439 | final Map<String, Object> mechanismProperties) {
|
379 | 440 |
|
380 |
| - if (userName == null && !Arrays.asList(MONGODB_X509, MONGODB_AWS).contains(mechanism)) { |
| 441 | + if (mechanism == MONGODB_OIDC) { |
| 442 | + validateOidcCredentialConstruction(source, mechanismProperties); |
| 443 | + } |
| 444 | + |
| 445 | + if (userName == null && !Arrays.asList(MONGODB_X509, MONGODB_AWS, MONGODB_OIDC).contains(mechanism)) { |
381 | 446 | throw new IllegalArgumentException("username can not be null");
|
382 | 447 | }
|
383 | 448 |
|
@@ -552,4 +617,135 @@ public String toString() {
|
552 | 617 | + ", mechanismProperties=<hidden>"
|
553 | 618 | + '}';
|
554 | 619 | }
|
| 620 | + |
| 621 | + // TODO-OIDC |
| 622 | + public interface RequestCallback { |
| 623 | + OidcTokens callback( |
| 624 | + @Nullable String principalName, |
| 625 | + IdpServerInfo serverInfo, |
| 626 | + int timeoutSeconds); |
| 627 | + } |
| 628 | + |
| 629 | + public interface RefreshCallback { |
| 630 | + OidcTokens callback( |
| 631 | + @Nullable String principalName, |
| 632 | + IdpServerInfo serverInfo, |
| 633 | + OidcTokens tokens, |
| 634 | + int timeoutSeconds); |
| 635 | + } |
| 636 | + |
| 637 | + /** |
| 638 | + * Server's reply to clientStep1. |
| 639 | + * <p> |
| 640 | + * IDP's configuration will be returned in serverStep1 to instruct the client |
| 641 | + * on how to acquire an Access Token. |
| 642 | + */ |
| 643 | + public static class IdpServerInfo { |
| 644 | + private final String issuer; |
| 645 | + private final String clientId; |
| 646 | + @Nullable |
| 647 | + private final List<String> requestScopes; |
| 648 | + |
| 649 | + /** |
| 650 | + * URL which describes the Authorization Server. This identifier should |
| 651 | + * be the iss of provided access tokens, and be viable for RFC8414 |
| 652 | + * metadata discovery and RFC9207 identification. |
| 653 | + */ |
| 654 | + public String getIssuer() { |
| 655 | + return issuer; |
| 656 | + } |
| 657 | + |
| 658 | + /** |
| 659 | + * Unique client ID for this OIDC client |
| 660 | + */ |
| 661 | + public String getClientId() { |
| 662 | + return clientId; |
| 663 | + } |
| 664 | + |
| 665 | + /** |
| 666 | + * Additional scopes to request from IDP |
| 667 | + */ |
| 668 | + @Nullable |
| 669 | + public List<String> getRequestScopes() { |
| 670 | + return requestScopes; |
| 671 | + } |
| 672 | + |
| 673 | + public IdpServerInfo(final Bson bson) { |
| 674 | + // TODO-OIDC move to internal? |
| 675 | + BsonDocument c = bson.toBsonDocument(); |
| 676 | + this.issuer = getString(c, "issuer"); |
| 677 | + this.clientId = getString(c, "clientId"); |
| 678 | + this.requestScopes = getStringArray(c, "requestScopes"); |
| 679 | + } |
| 680 | + |
| 681 | + @Nullable |
| 682 | + private static String getString(final BsonDocument document, final String key) { |
| 683 | + if (!document.containsKey(key) || document.isString(key)) { |
| 684 | + return null; |
| 685 | + } |
| 686 | + return document.getString(key).getValue(); |
| 687 | + } |
| 688 | + |
| 689 | + @Nullable |
| 690 | + private static List<String> getStringArray(final BsonDocument document, final String key) { |
| 691 | + if (!document.containsKey(key) || document.isString(key)) { |
| 692 | + return null; |
| 693 | + } |
| 694 | + List<String> result = document.getArray(key).getValues().stream() |
| 695 | + // ignore non-string values from server, rather than error |
| 696 | + .filter(v -> v.isString()) |
| 697 | + .map(v -> v.asString().getValue()) |
| 698 | + .collect(Collectors.toList()); |
| 699 | + return Collections.unmodifiableList(result); |
| 700 | + } |
| 701 | + } |
| 702 | + |
| 703 | + /** |
| 704 | + * The result of a token request |
| 705 | + */ |
| 706 | + public static class OidcTokens { |
| 707 | + |
| 708 | + private final String accessToken; |
| 709 | + |
| 710 | + private final Integer expiresInSeconds; |
| 711 | + |
| 712 | + private final String refreshToken; |
| 713 | + |
| 714 | + /** |
| 715 | + * The OIDC access token |
| 716 | + */ |
| 717 | + public String getAccessToken() { |
| 718 | + return accessToken; |
| 719 | + } |
| 720 | + |
| 721 | + /** |
| 722 | + * The expiration time in seconds from the current time |
| 723 | + */ |
| 724 | + @Nullable |
| 725 | + public Integer getExpiresInSeconds() { |
| 726 | + return expiresInSeconds; |
| 727 | + } |
| 728 | + |
| 729 | + /** |
| 730 | + * The OIDC refresh token |
| 731 | + */ |
| 732 | + @Nullable |
| 733 | + public String getRefreshToken() { |
| 734 | + return refreshToken; |
| 735 | + } |
| 736 | + |
| 737 | + public OidcTokens( |
| 738 | + final String accessToken, |
| 739 | + @Nullable final Integer expiresInSeconds, |
| 740 | + @Nullable final String refreshToken) { |
| 741 | + this.accessToken = accessToken; |
| 742 | + this.expiresInSeconds = expiresInSeconds; |
| 743 | + this.refreshToken = refreshToken; |
| 744 | + } |
| 745 | + |
| 746 | + public final BsonDocument toBsonDocument() { // TODO-OIDC move to internal? |
| 747 | + return new BsonDocument() |
| 748 | + .append("jwt", new BsonString(getAccessToken())); |
| 749 | + } |
| 750 | + } |
555 | 751 | }
|
0 commit comments