From 95176693b70365cb04725e8374e5cf0d77d1c397 Mon Sep 17 00:00:00 2001 From: Manuel Fink Date: Tue, 30 Jun 2026 10:33:36 +0200 Subject: [PATCH 1/2] increase mock jwt lifetime from 1h to 24h --- .../config/TestSecurityContextHelper.java | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/ams-cap-bookshop/srv/src/test/java/customer/ams_cap_bookshop/customization/config/TestSecurityContextHelper.java b/ams-cap-bookshop/srv/src/test/java/customer/ams_cap_bookshop/customization/config/TestSecurityContextHelper.java index 5c9246d..6d30c99 100644 --- a/ams-cap-bookshop/srv/src/test/java/customer/ams_cap_bookshop/customization/config/TestSecurityContextHelper.java +++ b/ams-cap-bookshop/srv/src/test/java/customer/ams_cap_bookshop/customization/config/TestSecurityContextHelper.java @@ -44,6 +44,21 @@ public class TestSecurityContextHelper { */ public static final String SERVICE_BINDING_ROOT = "src/test/resources/customization/service-bindings"; + /** + * Token TTL used for mocked JWTs in tests. + * + *

Set to 24 hours rather than the typical 1 hour because of a latent timezone bug in + * {@code com.sap.cloud.security:java-api}'s {@code Token.isExpired()} implementation, which + * compares the parsed {@code exp} claim against {@code LocalDateTime.now().toInstant(ZoneOffset.UTC)}. + * On a JVM whose default zone is UTC+N (e.g. CEST = UTC+2), that expression is N hours ahead + * of the real {@code Instant.now()}, so a token minted with a TTL shorter than N is reported + * as already expired the moment it's created. + * + *

Using 24h is safely larger than the maximum civil offset on Earth (+14h, Kiribati) and + * therefore makes these tests robust against the bug regardless of the JVM's default zone. + */ + private static final long TOKEN_TTL_SECONDS = 24L * 60 * 60; + private final CdsRuntime cdsRuntime; public TestSecurityContextHelper(CdsRuntime cdsRuntime) { @@ -263,8 +278,8 @@ public static String createXsuaaJwt(String userName, String... scopes) { """, userName, scopesJson, - System.currentTimeMillis() / 1000 + 3600, // expires in 1 hour - System.currentTimeMillis() / 1000, // issued now + System.currentTimeMillis() / 1000 + TOKEN_TTL_SECONDS, // exp + System.currentTimeMillis() / 1000, // iat userName); return encodeJwt(payload); @@ -306,13 +321,13 @@ public static String createIasJwt(String userName, String scimId) { "email": "%s@example.com" } """, - scimId, // sub (same as scim_id for user tokens) - TEST_TENANT_ID, // app_tid (tenant ID) - scimId, // scim_id (user identifier for policy lookup) - scimId, // user_uuid - System.currentTimeMillis() / 1000 + 3600, // exp (expires in 1 hour) - System.currentTimeMillis() / 1000, // iat (issued now) - userName); // email + scimId, // sub (same as scim_id for user tokens) + TEST_TENANT_ID, // app_tid (tenant ID) + scimId, // scim_id (user identifier for policy lookup) + scimId, // user_uuid + System.currentTimeMillis() / 1000 + TOKEN_TTL_SECONDS, // exp + System.currentTimeMillis() / 1000, // iat + userName); // email return encodeJwt(payload); } From f8ead279dcf2edade02817bebf1695470dc6782a Mon Sep 17 00:00:00 2001 From: Manuel Fink Date: Tue, 30 Jun 2026 10:35:57 +0200 Subject: [PATCH 2/2] use simple mocked XsuaaExtension to disable cache invalidation --- ...HybridAuthorizationsTestConfiguration.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ams-cap-bookshop/srv/src/test/java/customer/ams_cap_bookshop/customization/config/HybridAuthorizationsTestConfiguration.java b/ams-cap-bookshop/srv/src/test/java/customer/ams_cap_bookshop/customization/config/HybridAuthorizationsTestConfiguration.java index 1670d6d..00ccf7d 100644 --- a/ams-cap-bookshop/srv/src/test/java/customer/ams_cap_bookshop/customization/config/HybridAuthorizationsTestConfiguration.java +++ b/ams-cap-bookshop/srv/src/test/java/customer/ams_cap_bookshop/customization/config/HybridAuthorizationsTestConfiguration.java @@ -3,6 +3,9 @@ import com.sap.cloud.security.ams.api.*; import com.sap.cloud.security.ams.cap.api.CdsAuthorizations; import com.sap.cloud.security.ams.core.HybridAuthorizationsProvider; +import com.sap.cloud.security.token.SecurityContext; +import com.sap.cloud.security.token.XsuaaTokenExtension; +import jakarta.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; @@ -42,13 +45,25 @@ public class HybridAuthorizationsTestConfiguration { private static final Logger LOG = LoggerFactory.getLogger(HybridAuthorizationsTestConfiguration.class); private static final String XSAPPNAME = "bookshop"; + /** + * Replaces the auto-configured {@code DefaultXsuaaTokenExtension} with a pass-through that + * returns the {@link com.sap.cloud.security.token.Token} previously placed by + * {@code SecurityContext.setXsuaaToken(...)} as-is without cache invalidation logic. + */ + @PostConstruct + void installPassThroughXsuaaTokenExtension() { + XsuaaTokenExtension passThrough = token -> token; + SecurityContext.registerXsuaaTokenExtension(passThrough); + LOG.info("Registered pass-through XsuaaTokenExtension; production DefaultXsuaaTokenExtension will not be used."); + } + @Bean @Primary public AuthorizationsProvider hybridCdsAuthorizationsProvider( AuthorizationManagementService ams, @Value("${ams.combined-authorizations-enabled:false}") boolean combinedAuthorizationsEnabled) { - LOG.info("Creating HybridAuthorizationsProvider with combinedAuthorizationsEnabled={}", + LOG.info("Creating HybridAuthorizationsProvider with combinedAuthorizationsEnabled={}", combinedAuthorizationsEnabled); ScopeMapper scopeMapper = ScopeMapper.ofFunctionMultiple(scope -> switch (scope) { @@ -61,10 +76,10 @@ public AuthorizationsProvider hybridCdsAuthorizationsProvider .withXsAppName(XSAPPNAME) .withCombinedAuthorizationsEnabled(combinedAuthorizationsEnabled); } - + /** * Provides mock policy assignments for IAS users. - * + * *

This bean is used when combined authorizations is enabled to simulate * policy assignments that would normally come from the AMS service. */