diff --git a/cloudplatform/cloudplatform-connectivity/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServiceOptions.java b/cloudplatform/cloudplatform-connectivity/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServiceOptions.java index daf6edf89..7acb8f029 100644 --- a/cloudplatform/cloudplatform-connectivity/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServiceOptions.java +++ b/cloudplatform/cloudplatform-connectivity/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServiceOptions.java @@ -324,6 +324,23 @@ public static OptionsEnhancer withConsumerClient( @Nonnull final String consu return new IasCommunicationOptions(null, null, null, consumerClientId, consumerTenantId); } + /** + * Specifies the token format to request from IAS during token exchange. + *

+ * If not specified, the {@code token_format} parameter is not included in the token exchange request, and IAS + * will use its default behavior (SAML). Explicitly set to {@code "jwt"} if the target service requires JWT + * tokens. + * + * @param format + * The token format to request. Typically {@code "jwt"} or {@code "saml"}. + * @return An instance of {@link OptionsEnhancer} for the token format. + */ + @Nonnull + public static OptionsEnhancer withTokenFormat( @Nonnull final String format ) + { + return new TokenFormat(format); + } + /** * An {@link OptionsEnhancer} that contains the target URI for an IAS-based destination. Also refer to * {@link #withTargetUri(String)}. @@ -382,5 +399,18 @@ public IasCommunicationOptions getValue() return this; } } + + /** + * An {@link OptionsEnhancer} that specifies the token format for IAS token requests. If not provided, the + * {@code token_format} parameter is not included in the token exchange request. Also refer to + * {@link #withTokenFormat(String)}. + */ + @Value + @AllArgsConstructor( access = AccessLevel.PRIVATE ) + public static class TokenFormat implements OptionsEnhancer + { + @Nonnull + String value; + } } } diff --git a/cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliers.java b/cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliers.java index 2833be740..2e112c0b5 100644 --- a/cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliers.java +++ b/cloudplatform/connectivity-oauth/src/main/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliers.java @@ -2,6 +2,7 @@ import static com.sap.cloud.sdk.cloudplatform.connectivity.BtpServiceOptions.AuthenticationServiceOptions.TargetUri; import static com.sap.cloud.sdk.cloudplatform.connectivity.BtpServiceOptions.IasOptions.IasCommunicationOptions; +import static com.sap.cloud.sdk.cloudplatform.connectivity.BtpServiceOptions.IasOptions.TokenFormat; import static com.sap.cloud.sdk.cloudplatform.connectivity.MultiUrlPropertySupplier.REMOVE_PATH; import java.net.URI; @@ -189,6 +190,9 @@ public OAuth2Options getOAuth2Options() } else { attachIasCommunicationOptions(builder); builder.withTokenRetrievalParameter("app_tid", getCredentialOrThrow(String.class, "app_tid")); + options + .getOption(TokenFormat.class) + .peek(format -> builder.withTokenRetrievalParameter("token_format", format)); } attachClientKeyStore(builder); diff --git a/cloudplatform/connectivity-oauth/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliersTest.java b/cloudplatform/connectivity-oauth/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliersTest.java index 6afd3fef1..ae9da1462 100644 --- a/cloudplatform/connectivity-oauth/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliersTest.java +++ b/cloudplatform/connectivity-oauth/src/test/java/com/sap/cloud/sdk/cloudplatform/connectivity/BtpServicePropertySuppliersTest.java @@ -789,6 +789,75 @@ void testMutuallyExclusiveOptions() } } + @Test + void testTokenFormatExplicitJwt() + { + final ServiceBindingDestinationOptions options = + ServiceBindingDestinationOptions + .forService(BINDING) + .onBehalfOf(OnBehalfOf.NAMED_USER_CURRENT_TENANT) + .withOption(IasOptions.withApplicationName("app-name")) + .withOption(IasOptions.withTokenFormat("jwt")) + .build(); + + final OAuth2PropertySupplier sut = IDENTITY_AUTHENTICATION.resolve(options); + final OAuth2Options oAuth2Options = sut.getOAuth2Options(); + + assertThat(oAuth2Options.getAdditionalTokenRetrievalParameters()) + .containsExactlyInAnyOrderEntriesOf( + Map + .of( + "resource", + "urn:sap:identity:application:provider:name:app-name", + "app_tid", + PROVIDER_TENANT_ID, + "token_format", + "jwt")); + } + + @Test + void testTokenFormatExplicitSaml() + { + final ServiceBindingDestinationOptions options = + ServiceBindingDestinationOptions + .forService(BINDING) + .onBehalfOf(OnBehalfOf.NAMED_USER_CURRENT_TENANT) + .withOption(IasOptions.withApplicationName("app-name")) + .withOption(IasOptions.withTokenFormat("saml")) + .build(); + + final OAuth2PropertySupplier sut = IDENTITY_AUTHENTICATION.resolve(options); + final OAuth2Options oAuth2Options = sut.getOAuth2Options(); + + assertThat(oAuth2Options.getAdditionalTokenRetrievalParameters()) + .containsExactlyInAnyOrderEntriesOf( + Map + .of( + "resource", + "urn:sap:identity:application:provider:name:app-name", + "app_tid", + PROVIDER_TENANT_ID, + "token_format", + "saml")); + } + + @Test + void testTokenFormatWithoutApplicationName() + { + final ServiceBindingDestinationOptions options = + ServiceBindingDestinationOptions + .forService(BINDING) + .onBehalfOf(OnBehalfOf.NAMED_USER_CURRENT_TENANT) + .withOption(IasOptions.withTokenFormat("jwt")) + .build(); + + final OAuth2PropertySupplier sut = IDENTITY_AUTHENTICATION.resolve(options); + final OAuth2Options oAuth2Options = sut.getOAuth2Options(); + + assertThat(oAuth2Options.getAdditionalTokenRetrievalParameters()) + .containsExactlyInAnyOrderEntriesOf(Map.of("app_tid", PROVIDER_TENANT_ID, "token_format", "jwt")); + } + @SneakyThrows private static void assertThatClientCertificateIsContained( @Nonnull final KeyStore keyStore ) {