Skip to content

Commit

Permalink
feat: IAS App-to-Service Communication (#331)
Browse files Browse the repository at this point in the history
Co-authored-by: Matthias Kuhr <52661546+MatKuhr@users.noreply.github.com>
  • Loading branch information
Johannes Schneider and MatKuhr authored Mar 8, 2024
1 parent 78c1f60 commit 2d9b1ee
Show file tree
Hide file tree
Showing 8 changed files with 620 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -142,29 +142,21 @@ public static OptionsEnhancer<?> withTargetUri( @Nonnull final URI targetUri )
}

/**
* Creates an {@link OptionsEnhancer} that instructs the destination to use mTLS authentication only.
* <p>
* In the regular OAuth2 case, setting this option will skip the IAS token flow.
* <p>
* <b>Hint:</b> This option is <b>mutually exclusive</b> with {@link #withApplicationName(String)} and
* {@link #withConsumerClient(String, String)}.
* <p>
* <b>Caution:</b> This option cannot be combined with {@link OnBehalfOf#NAMED_USER_CURRENT_TENANT}.
* Creates an instance of {@link NoTokenForTechnicalProviderUser}.
*
* @return An instance of {@link OptionsEnhancer} that will lead to mTLS authentication only.
* @return A new {@link NoTokenForTechnicalProviderUser} instance.
*/
@Nonnull
public static OptionsEnhancer<?> withMTLSAuthenticationOnly()
public static OptionsEnhancer<?> withoutTokenForTechnicalProviderUser()
{
return new IasCommunicationOptions(null, null, null, true);
return new NoTokenForTechnicalProviderUser(true);
}

/**
* Creates an {@link OptionsEnhancer} that instructs an IAS-based destination to use the given application
* provider name when performing token retrievals. This is needed in <b>App-To-App</b> communication scenarios.
* <p>
* <b>Hint:</b> This option is <b>mutually exclusive</b> with {@link #withMTLSAuthenticationOnly()} and
* {@link #withConsumerClient(String, String)}.
* <b>Hint:</b> This option is <b>mutually exclusive</b> with {@link #withConsumerClient(String, String)}.
*
* @param applicationName
* The name of the application provider to be used. This is the name that was used to register the
Expand All @@ -175,15 +167,14 @@ public static OptionsEnhancer<?> withMTLSAuthenticationOnly()
@Nonnull
public static OptionsEnhancer<?> withApplicationName( @Nonnull final String applicationName )
{
return new IasCommunicationOptions(applicationName, null, null, false);
return new IasCommunicationOptions(applicationName, null, null);
}

/**
* Creates an {@link OptionsEnhancer} that instructs an IAS-based destination to use the given consumer client
* ID when performing token retrievals. This is needed in <i>Service-To-App</i> communication scenarios.
* <p>
* <b>Hint:</b> This option is <b>mutually exclusive</b> with {@link #withMTLSAuthenticationOnly()} and
* {@link #withApplicationName(String)}.
* <b>Hint:</b> This option is <b>mutually exclusive</b> with {@link #withApplicationName(String)}.
*
* @param consumerClientId
* The client ID of the consumer application. This client ID is usually extracted from an incoming
Expand All @@ -194,16 +185,15 @@ public static OptionsEnhancer<?> withApplicationName( @Nonnull final String appl
@Nonnull
public static OptionsEnhancer<?> withConsumerClient( @Nonnull final String consumerClientId )
{
return new IasCommunicationOptions(null, consumerClientId, null, false);
return new IasCommunicationOptions(null, consumerClientId, null);
}

/**
* Creates an {@link OptionsEnhancer} that instructs an IAS-based destination to use the given consumer client
* and tenant ID when performing token retrievals. This is needed in <i>Service-To-App</i> communication
* scenarios.
* <p>
* <b>Hint:</b> This option is <b>mutually exclusive</b> with {@link #withMTLSAuthenticationOnly()} and
* {@link #withApplicationName(String)}.
* <b>Hint:</b> This option is <b>mutually exclusive</b> with {@link #withApplicationName(String)}.
*
* @param consumerClientId
* The client ID of the consumer application. This client ID is usually extracted from an incoming
Expand All @@ -219,7 +209,7 @@ public static OptionsEnhancer<?> withConsumerClient( @Nonnull final String consu
OptionsEnhancer<?>
withConsumerClient( @Nonnull final String consumerClientId, @Nonnull final String consumerTenantId )
{
return new IasCommunicationOptions(null, consumerClientId, consumerTenantId, false);
return new IasCommunicationOptions(null, consumerClientId, consumerTenantId);
}

/**
Expand All @@ -233,9 +223,26 @@ public static class IasTargetUri implements OptionsEnhancer<URI>
URI value;
}

/**
* An {@link OptionsEnhancer} that indicates whether <b>no</b> token is required for authenticating at the
* target system <b>iff</b> the authentication happens on behalf of a <b>technical provider user</b>. In this
* case, retrieving a token from the IAS service is optional and, therefore, can be skipped. Instead,
* authentication is done via mTLS only. In every other case (i.e. <i>named user</i> for either the provider or
* a subscriber, or <i>technical user</i> on behalf of a subscriber), a token is always required to not lose any
* tenancy information at the target system.
*
* @since 5.6.0
*/
@Value
@AllArgsConstructor( access = AccessLevel.PRIVATE )
public static class NoTokenForTechnicalProviderUser implements OptionsEnhancer<Boolean>
{
Boolean value;
}

/**
* An {@link OptionsEnhancer} that contains the communication options for an IAS-based destination. Also refer
* to {@link #withMTLSAuthenticationOnly()}, {@link #withApplicationName(String)}, and
* to {@link #withoutTokenForTechnicalProviderUser()}, {@link #withApplicationName(String)}, and
* {@link #withConsumerClient(String, String)}.
*/
@Value
Expand All @@ -248,7 +255,6 @@ public static class IasCommunicationOptions implements OptionsEnhancer<IasCommun
String consumerClientId;
@Nullable
String consumerTenantId;
boolean mTLSAuthenticationOnly;

@Nonnull
@Override
Expand Down
5 changes: 5 additions & 0 deletions cloudplatform/connectivity-oauth/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@
<scope>runtime</scope>
</dependency>
<!-- scope "test" -->
<dependency>
<groupId>com.sap.cloud.environment.servicebinding.api</groupId>
<artifactId>java-access-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
<artifactId>connectivity-apache-httpclient4</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.sap.cloud.sdk.cloudplatform.connectivity.BtpServiceOptions.BusinessRulesOptions;
import com.sap.cloud.sdk.cloudplatform.connectivity.BtpServiceOptions.WorkflowOptions;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.tenant.Tenant;
import com.sap.cloud.sdk.cloudplatform.tenant.TenantAccessor;
import com.sap.cloud.security.config.ClientCertificate;
import com.sap.cloud.security.config.ClientIdentity;
import com.sap.cloud.security.mtls.SSLContextFactory;
Expand Down Expand Up @@ -164,7 +166,11 @@ public OAuth2Options getOAuth2Options()
{
final OAuth2Options.Builder oAuth2OptionsBuilder = OAuth2Options.builder();

attachIasCommunicationOptions(oAuth2OptionsBuilder);
if( skipTokenRetrieval() ) {
oAuth2OptionsBuilder.withSkipTokenRetrieval(true);
} else {
attachIasCommunicationOptions(oAuth2OptionsBuilder);
}
attachClientKeyStore(oAuth2OptionsBuilder);

return oAuth2OptionsBuilder.build();
Expand All @@ -177,11 +183,6 @@ private void attachIasCommunicationOptions( @Nonnull final OAuth2Options.Builder
return;
}

if( o.isMTLSAuthenticationOnly() && mTLSOnlyIsSupported() ) {
optionsBuilder.withSkipTokenRetrieval(true);
return;
}

if( o.getApplicationName() != null ) {
optionsBuilder
.withTokenRetrievalParameter(
Expand All @@ -200,19 +201,34 @@ private void attachIasCommunicationOptions( @Nonnull final OAuth2Options.Builder
}
}

private boolean mTLSOnlyIsSupported()
private boolean skipTokenRetrieval()
{
final OnBehalfOf behalf = options.getOnBehalfOf();
if( behalf == OnBehalfOf.NAMED_USER_CURRENT_TENANT ) {
log.warn("""
Combining {} with mTLS only authentication is not supported. \
This is because mTLS only works without sending any authentication token. \
But without an authentication token, user information cannot be preserved.\
""", OnBehalfOf.NAMED_USER_CURRENT_TENANT);
final Boolean noTokenRequired =
options.getOption(BtpServiceOptions.IasOptions.NoTokenForTechnicalProviderUser.class).getOrElse(false);

final boolean tokenIsAlwaysRequired = !noTokenRequired;
if( tokenIsAlwaysRequired ) {
return false;
}

return true;
return switch( behalf ) {
case NAMED_USER_CURRENT_TENANT -> false;
case TECHNICAL_USER_PROVIDER -> true;
case TECHNICAL_USER_CURRENT_TENANT -> currentTenantIsProvider();
};
}

private boolean currentTenantIsProvider()
{
final String maybeTenantId = TenantAccessor.tryGetCurrentTenant().map(Tenant::getTenantId).getOrNull();
if( maybeTenantId == null ) {
// there is no current tenant --> assume we are running in the provider context
return true;
}

final String providerTenantId = getCredentialOrThrow(String.class, "app_tid");
return maybeTenantId.equalsIgnoreCase(providerTenantId);
}

private void attachClientKeyStore( @Nonnull final OAuth2Options.Builder optionsBuilder )
Expand Down
Loading

0 comments on commit 2d9b1ee

Please sign in to comment.