diff --git a/java-security-it/src/test/java/com/sap/cloud/security/test/integration/XsuaaIntegrationTest.java b/java-security-it/src/test/java/com/sap/cloud/security/test/integration/XsuaaIntegrationTest.java index 10c64ae872..ef5d75ce1a 100644 --- a/java-security-it/src/test/java/com/sap/cloud/security/test/integration/XsuaaIntegrationTest.java +++ b/java-security-it/src/test/java/com/sap/cloud/security/test/integration/XsuaaIntegrationTest.java @@ -27,7 +27,7 @@ import static com.sap.cloud.security.config.Service.XSUAA; import static com.sap.cloud.security.config.ServiceConstants.XSUAA.VERIFICATION_KEY; import static com.sap.cloud.security.test.SecurityTestRule.DEFAULT_CLIENT_ID; -import static com.sap.cloud.security.test.SecurityTestRule.DEFAULT_DOMAIN; +import static com.sap.cloud.security.test.SecurityTestRule.DEFAULT_UAA_DOMAIN; import static org.assertj.core.api.Assertions.assertThat; /** @@ -51,7 +51,7 @@ public void xsuaaTokenValidationSucceeds_withXsuaaCombiningValidator() { } @Test - public void xsaTokenValidationSucceeds_withXsuaaCombiningValidator() throws IOException { + public void xsaTokenValidationSucceeds_withXsuaaCombiningValidator() { OAuth2ServiceConfiguration configuration = rule.getOAuth2ServiceConfigurationBuilderFromFile( "/xsa-simple/vcap_services-single.json") .runInLegacyMode(true) @@ -105,7 +105,7 @@ public void createToken_withCorrectVerificationKey_tokenIsValid() throws IOExcep String publicKey = IOUtils.resourceToString("/publicKey.txt", StandardCharsets.UTF_8); OAuth2ServiceConfiguration configuration = OAuth2ServiceConfigurationBuilder .forService(XSUAA) - .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, DEFAULT_DOMAIN) + .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, DEFAULT_UAA_DOMAIN) .withClientId(DEFAULT_CLIENT_ID) .withProperty(VERIFICATION_KEY, publicKey) .build(); diff --git a/java-security-it/src/test/java/com/sap/cloud/security/test/integration/XsuaaMultipleBindingsIntegrationTest.java b/java-security-it/src/test/java/com/sap/cloud/security/test/integration/XsuaaMultipleBindingsIntegrationTest.java index a05f5d2bb8..7fbe16ee55 100644 --- a/java-security-it/src/test/java/com/sap/cloud/security/test/integration/XsuaaMultipleBindingsIntegrationTest.java +++ b/java-security-it/src/test/java/com/sap/cloud/security/test/integration/XsuaaMultipleBindingsIntegrationTest.java @@ -8,6 +8,7 @@ import com.sap.cloud.security.config.Environments; import com.sap.cloud.security.config.OAuth2ServiceConfiguration; import com.sap.cloud.security.config.Service; +import com.sap.cloud.security.config.ServiceConstants; import com.sap.cloud.security.test.SecurityTestRule; import com.sap.cloud.security.token.Token; import com.sap.cloud.security.token.validation.CombiningValidator; @@ -15,6 +16,7 @@ import com.sap.cloud.security.token.validation.validators.JwtValidatorBuilder; import org.junit.ClassRule; import org.junit.Test; +import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; @@ -31,7 +33,16 @@ public class XsuaaMultipleBindingsIntegrationTest { public void createToken_integrationTest_tokenValidation() { Token token = rule.getPreconfiguredJwtGenerator().createToken(); OAuth2ServiceConfiguration configuration = Environments.readFromInput(XsuaaMultipleBindingsIntegrationTest.class.getResourceAsStream("/vcap_services-multiple.json")).getXsuaaConfiguration(); - CombiningValidator tokenValidator = JwtValidatorBuilder.getInstance(configuration).build(); + OAuth2ServiceConfiguration mockConfig = Mockito.mock(OAuth2ServiceConfiguration.class); + Mockito.when(mockConfig.getClientId()).thenReturn(configuration.getClientId()); + Mockito.when(mockConfig.getDomains()).thenReturn(configuration.getDomains()); + Mockito.when(mockConfig.getUrl()).thenReturn(configuration.getUrl()); + Mockito.when(mockConfig.hasProperty(ServiceConstants.XSUAA.APP_ID)).thenReturn(configuration.hasProperty(ServiceConstants.XSUAA.APP_ID)); + Mockito.when(mockConfig.getProperty(ServiceConstants.XSUAA.APP_ID)).thenReturn(configuration.getProperty(ServiceConstants.XSUAA.APP_ID)); + Mockito.when(mockConfig.getProperty(ServiceConstants.XSUAA.UAA_DOMAIN)).thenReturn(rule.getWireMockServer().baseUrl()); + Mockito.when(mockConfig.getService()).thenReturn(configuration.getService()); + + CombiningValidator tokenValidator = JwtValidatorBuilder.getInstance(mockConfig).build(); ValidationResult result = tokenValidator.validate(token); assertThat(result.isValid()).isTrue(); diff --git a/java-security-it/src/test/java/com/sap/cloud/security/test/integration/ssrf/JavaSSRFAttackTest.java b/java-security-it/src/test/java/com/sap/cloud/security/test/integration/ssrf/JavaSSRFAttackTest.java index c05dd08b21..fce6a15a55 100644 --- a/java-security-it/src/test/java/com/sap/cloud/security/test/integration/ssrf/JavaSSRFAttackTest.java +++ b/java-security-it/src/test/java/com/sap/cloud/security/test/integration/ssrf/JavaSSRFAttackTest.java @@ -5,13 +5,10 @@ */ package com.sap.cloud.security.test.integration.ssrf; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; - -import java.io.IOException; - import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder; import com.sap.cloud.security.config.Service; +import com.sap.cloud.security.config.ServiceConstants; +import com.sap.cloud.security.test.RSAKeys; import com.sap.cloud.security.test.extension.SecurityTestExtension; import com.sap.cloud.security.token.Token; import com.sap.cloud.security.token.TokenHeader; @@ -28,6 +25,13 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; + /** * Test cases for SSRF @@ -56,15 +60,25 @@ class JavaSSRFAttackTest { @CsvSource({ "http://localhost:4242/token_keys, true", "http://localhost:4242/token_keys@malicious.ondemand.com/token_keys, false", - "http://user@localhost:4242/token_keys, true", // user info in URI is deprecated by Apache HttpClient 5, but working in HttpClient 4.x.x "http://localhost:4242/token_keys///malicious.ondemand.com/token_keys, false", }) - void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean isValid) throws IOException { - OAuth2ServiceConfigurationBuilder configuration = extension.getContext() - .getOAuth2ServiceConfigurationBuilderFromFile("/xsuaa/vcap_services-single.json"); - Token token = extension.getContext().getJwtGeneratorFromFile("/xsuaa/token.json") - .withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl) - .createToken(); + void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean isValid) + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + OAuth2ServiceConfigurationBuilder configuration = + extension.getContext() + .getOAuth2ServiceConfigurationBuilderFromFile("/xsuaa/vcap_services-single.json") + .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, extension.getContext().getWireMockServer().baseUrl()); + Token token; + if (isValid) { + token = extension.getContext().getJwtGeneratorFromFile("/xsuaa/token.json") + .withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl) + .createToken(); + } else { + token = extension.getContext().getJwtGeneratorFromFile("/xsuaa/token.json") + .withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl) + .withPrivateKey(RSAKeys.loadPrivateKey("/random_private_key.txt")) + .createToken(); + } CombiningValidator tokenValidator = JwtValidatorBuilder .getInstance(configuration.build()) .withHttpClient(httpClient) diff --git a/java-security-it/src/test/java/com/sap/cloud/security/test/integration/ssrf/SpringSSRFAttackTest.java b/java-security-it/src/test/java/com/sap/cloud/security/test/integration/ssrf/SpringSSRFAttackTest.java index 1e0c9ae081..5f1087725b 100644 --- a/java-security-it/src/test/java/com/sap/cloud/security/test/integration/ssrf/SpringSSRFAttackTest.java +++ b/java-security-it/src/test/java/com/sap/cloud/security/test/integration/ssrf/SpringSSRFAttackTest.java @@ -6,6 +6,7 @@ package com.sap.cloud.security.test.integration.ssrf; import com.sap.cloud.security.config.Service; +import com.sap.cloud.security.test.RSAKeys; import com.sap.cloud.security.test.SecurityTest; import com.sap.cloud.security.test.extension.SecurityTestExtension; import com.sap.cloud.security.token.TokenHeader; @@ -24,6 +25,8 @@ import org.springframework.web.client.RestTemplate; import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; import static org.assertj.core.api.Assertions.assertThat; @@ -32,7 +35,7 @@ * "https://owasp.org/www-community/attacks/Server_Side_Request_Forgery">SSRF * (Server Side Request Forgery) attacks. */ -public class SpringSSRFAttackTest { +class SpringSSRFAttackTest { private RestOperations restOperations = Mockito.spy(new RestTemplate()); @@ -56,11 +59,21 @@ public class SpringSSRFAttackTest { "http://malicious.ondemand.com@localhost:4242/token_keys, true", "http://localhost:4242/token_keys///malicious.ondemand.com/token_keys, false", }) - public void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean isValid) throws IOException { - String token = extension.getContext().getPreconfiguredJwtGenerator() - .withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl) - .createToken() - .getTokenValue(); + void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean isValid) + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + String token; + if (isValid) { + token = extension.getContext().getPreconfiguredJwtGenerator() + .withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl) + .createToken() + .getTokenValue(); + } else { + token = extension.getContext().getPreconfiguredJwtGenerator() + .withHeaderParameter(TokenHeader.JWKS_URL, jwksUrl) + .withPrivateKey(RSAKeys.loadPrivateKey("/random_private_key.txt")) + .createToken() + .getTokenValue(); + } JwtDecoder jwtDecoder = new XsuaaJwtDecoderBuilder( new XsuaaServiceConfigurationCustom(createXsuaaCredentials())) .withRestOperations(restOperations) @@ -80,7 +93,7 @@ public void maliciousPartOfJwksIsNotUsedToObtainToken(String jwksUrl, boolean is private XsuaaCredentials createXsuaaCredentials() { XsuaaCredentials xsuaaCredentials = new XsuaaCredentials(); - xsuaaCredentials.setUaaDomain(SecurityTest.DEFAULT_DOMAIN); + xsuaaCredentials.setUaaDomain(extension.getContext().getWireMockServer().baseUrl()); xsuaaCredentials.setClientId(SecurityTest.DEFAULT_CLIENT_ID); xsuaaCredentials.setXsAppName(SecurityTest.DEFAULT_APP_ID); return xsuaaCredentials; diff --git a/java-security-it/src/test/java/com/sap/cloud/security/test/performance/JavaSecurityPerformanceIT.java b/java-security-it/src/test/java/com/sap/cloud/security/test/performance/JavaSecurityPerformanceIT.java index d712d5a05c..91dc4dc4d2 100644 --- a/java-security-it/src/test/java/com/sap/cloud/security/test/performance/JavaSecurityPerformanceIT.java +++ b/java-security-it/src/test/java/com/sap/cloud/security/test/performance/JavaSecurityPerformanceIT.java @@ -58,7 +58,7 @@ void onlineValidation() { String tokenValue = token.getTokenValue(); BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> tokenValidator.validate(new XsuaaToken(tokenValue))); - LOGGER.info("Online validation result: {}", result.toString()); + LOGGER.info("Online validation result: {}", result); } @Test @@ -70,7 +70,7 @@ void offlineValidation() throws Exception { String tokenValue = token.getTokenValue(); BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> tokenValidator.validate(new XsuaaToken(tokenValue))); - LOGGER.info("Offline validation result: {}", result.toString()); + LOGGER.info("Offline validation result: {}", result); } private CombiningValidator createOfflineTokenValidator() throws IOException { @@ -90,7 +90,7 @@ private CombiningValidator createOnlineTokenValidator() { private OAuth2ServiceConfigurationBuilder createConfigurationBuilder() { return OAuth2ServiceConfigurationBuilder.forService(XSUAA) - .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, SecurityTest.DEFAULT_DOMAIN) + .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, securityTest.getWireMockServer().baseUrl()) .withProperty(ServiceConstants.XSUAA.APP_ID, SecurityTest.DEFAULT_APP_ID) .withClientId(SecurityTest.DEFAULT_CLIENT_ID); } diff --git a/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringSecurityPerformanceIT.java b/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringSecurityPerformanceIT.java index df1145053b..d0e1e48e9f 100644 --- a/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringSecurityPerformanceIT.java +++ b/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringSecurityPerformanceIT.java @@ -5,13 +5,11 @@ */ package com.sap.cloud.security.test.performance; -import com.sap.cloud.security.config.OAuth2ServiceConfiguration; import com.sap.cloud.security.config.OAuth2ServiceConfigurationBuilder; import com.sap.cloud.security.config.ServiceConstants; import com.sap.cloud.security.spring.token.authentication.JwtDecoderBuilder; import com.sap.cloud.security.test.SecurityTest; import com.sap.cloud.security.test.performance.util.BenchmarkUtil; -import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -19,9 +17,6 @@ import org.slf4j.LoggerFactory; import org.springframework.security.oauth2.jwt.JwtDecoder; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - import static com.sap.cloud.security.config.Service.IAS; import static com.sap.cloud.security.config.Service.XSUAA; import static org.assertj.core.api.Assertions.assertThat; @@ -57,7 +52,7 @@ void onlineValidation() { assertThat(jwtDecoder.decode(token)).isNotNull(); BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> jwtDecoder.decode(token)); - LOGGER.info("Online validation result (xsuaa): {}", result.toString()); + LOGGER.info("Online validation result (xsuaa): {}", result); } @Test @@ -67,17 +62,7 @@ void onlineIasValidation() { assertThat(jwtDecoder.decode(token)).isNotNull(); BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> jwtDecoder.decode(token)); - LOGGER.info("Online validation result (identity): {}", result.toString()); - } - - // @Test - void offlineValidation() throws Exception { - String token = securityTest.createToken().getTokenValue(); - JwtDecoder jwtDecoder = createOfflineJwtDecoder(); - assertThat(jwtDecoder.decode(token)).isNotNull(); - - BenchmarkUtil.Result result = BenchmarkUtil.execute(() -> jwtDecoder.decode(token)); - LOGGER.info("Offline validation result: {}", result.toString()); + LOGGER.info("Online validation result (identity): {}", result); } private JwtDecoder createOnlineJwtDecoder() { @@ -86,20 +71,9 @@ private JwtDecoder createOnlineJwtDecoder() { .withXsuaaServiceConfiguration(createXsuaaConfigurationBuilder().build()).build(); } - private JwtDecoder createOfflineJwtDecoder() throws IOException { - final String publicKey = IOUtils.resourceToString("/publicKey.txt", StandardCharsets.UTF_8) - .replace("\n", ""); - OAuth2ServiceConfiguration configuration = createXsuaaConfigurationBuilder() - .withProperty("verificationkey", publicKey) - .build(); - return new JwtDecoderBuilder() - .withIasServiceConfiguration(createIasConfigurationBuilder().build()) - .withXsuaaServiceConfiguration(configuration).build(); - } - private OAuth2ServiceConfigurationBuilder createXsuaaConfigurationBuilder() { return OAuth2ServiceConfigurationBuilder.forService(XSUAA) - .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, SecurityTest.DEFAULT_DOMAIN) + .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, securityTest.getWireMockServer().baseUrl()) .withProperty(ServiceConstants.XSUAA.APP_ID, SecurityTest.DEFAULT_APP_ID) .withClientId(SecurityTest.DEFAULT_CLIENT_ID); } diff --git a/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringXsuaaPerformanceIT.java b/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringXsuaaPerformanceIT.java index d5785940cd..4b5c3f300a 100644 --- a/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringXsuaaPerformanceIT.java +++ b/java-security-it/src/test/java/com/sap/cloud/security/test/performance/SpringXsuaaPerformanceIT.java @@ -77,7 +77,7 @@ private static XsuaaCredentials createXsuaaCredentials() throws IOException { final String publicKey = IOUtils.resourceToString("/publicKey.txt", StandardCharsets.UTF_8); XsuaaCredentials xsuaaCredentials = new XsuaaCredentials(); - xsuaaCredentials.setUaaDomain(SecurityTest.DEFAULT_DOMAIN); + xsuaaCredentials.setUaaDomain(SecurityTest.DEFAULT_UAA_DOMAIN); xsuaaCredentials.setClientId(SecurityTest.DEFAULT_CLIENT_ID); xsuaaCredentials.setXsAppName(SecurityTest.DEFAULT_APP_ID); xsuaaCredentials.setVerificationKey(publicKey.replace("\n", "")); diff --git a/java-security-it/src/test/resources/random_private_key.txt b/java-security-it/src/test/resources/random_private_key.txt new file mode 100644 index 0000000000..ec287ec048 --- /dev/null +++ b/java-security-it/src/test/resources/random_private_key.txt @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCbVBpnMyO0R8d2 +Kasc/f/Ziv7XPrzl6I5SXDJtFUb2LzCyA5SH49Qa5AvyGC6UtlZdTkxvAEtMQIAQ +xBtxFM1VOiWSriLrsQ/ol6wckgUANsrU2LQq4xw+6LI4u8MqIMQydgbVc/dfdYI1 ++wJVP1ihT6VYitmv9mwi9CuLyNzOvhGTKdtMGw9oA7KA9SWKmoOulp0w7WaiY0Jt +5r+joY+ffwvETDrT0i1+AMaEvp//JWJ3mkXNlBZv72XqYK4nDDSGeE7qC3pG/3w5 +YO3L0bR+tYA/IR+4hb0H6ZH/a8aHJT0httam8VeLL1FVtuwznfxMKN3kkXZ0m/HL +bhp10LihAgMBAAECggEAZVtbI052lPRlztBf7To9kqolozU4NFotTMcGzLGerZSb +lP3LFWVwid+Xf/GRq87Tym0GaURq3iYUq1wcgAzP9DZOQEnLVbsjo2YdlEMgakRW +1M9XucibLN3RNj4nmzzoafkkenMCz9KxFiJmIlSEtDZxsbZhWHZXl/N22u9GTs0o +KQNzroxI+SKxWcfrmJkOx3vL9++47/LY+Rw6dL+hkUxdxMLuhYUcYziNvRfV9o0Y +Ag7Pl85xL3N8HkHr5ELL0RKHyk+vKbZ9xhAH50mxTZG8tAj9Ds0v3hQJrTmuyAS3 +ZJkqkhIJtWHmhLYiKju9ObLXtVgm8wdg8+vq/u1utQKBgQDhEf6Sy0+DhEYTMLN+ +ioVf/rBXl8QgXbDkEoHMp+FhuYK3CdlD+pgaJq+KUc6RnHb0GeDPBcZkhRlTLxU0 +HtykDQFa4mcXIJaSKY8WHCF3hJLUnXYgQW+0oufXEDCORuzqgcUbEHnYpjuuzkCj +FqjCkH4lNdvW8IJ56rpjBaWyRwKBgQCwrJVWLPPZMuwXHlkM1ytAC+dsq/1cRo3D +by766k5u/J6xwlc3bM0LG6pHuXruBxkdKAeAkfmwCc4JSXR4JS3JNmYuQ7wbmDWp +20ABv9qFbTIt1rtEkjhV8bmamfe5qZL/0lza2KcQOZGr1wtzV/Vg384gm5oy1FSi +0isU+sCJ1wKBgQCFIAuf8Dm75MU+HJROyMhTG2ZaqR4Mtt4mSPwVfUdGcl/qvByS +pOrKrQ8vlWvFnPKPN69NRHEwi7mLBlJYXdjMABVJGJk5iMEG+yXzQfhZpUTkFa8F +LS9RfPn8r0rJHRKNMuzPMVOg3dJ3du+sh36SdrzmbZD29ZN3YWuVnoV/iQKBgDQM +5IJbBAx9gCjffATYb5mS6D+P/DjvYFyvqPurhCgWrPpZ8zAVEeOv5t7yulDeLnv0 +iyFJ4HIIsXby+SlcarzZFgmTUxweH9FHEvhw+YRNw3bVyJ5PJeHMMY5mxiEg4HoW +E9017yJMk6o41NrKkzRTO3tH3IoVHEpL+P1ZUthJAoGALDuPKfHiuXuxxGF4oeyH +KFFDIr991nBxUC1tB8Lff5ZStfbzTnjbzCRogsQ/pu1tBaoMQjpHhTnI3hbe/Iwf +VffTGJTxapTiEwQuSY2OaSgHtUrz4qurHos+uVTWni8TuXfqkeoc1aIr4D7ulzPN +O71jgCLNQW5OZD7MSn21eeU= +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/java-security-test/README.md b/java-security-test/README.md index ff6bfe3e98..41da6b6386 100644 --- a/java-security-test/README.md +++ b/java-security-test/README.md @@ -66,7 +66,7 @@ import static com.sap.cloud.security.test.SecurityTest.*; @SpringBootTest @AutoConfigureMockMvc @TestPropertySource(properties = { - "xsuaa.uaadomain=" + DEFAULT_DOMAIN, + "xsuaa.uaadomain=" + DEFAULT_UAA_DOMAIN, "xsuaa.xsappname=" + DEFAULT_APP_ID, "xsuaa.clientid=" + DEFAULT_CLIENT_ID }) @ExtendWith(XsuaaExtension.class) diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java b/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java index c42ca743a3..7d057998b1 100644 --- a/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java +++ b/java-security-test/src/main/java/com/sap/cloud/security/test/ApplicationServerOptions.java @@ -22,6 +22,7 @@ public class ApplicationServerOptions { private final TokenAuthenticator tokenAuthenticator; private final int port; + private static int tokenKeysPort; private ApplicationServerOptions(TokenAuthenticator tokenAuthenticator) { this(tokenAuthenticator, 0); @@ -57,7 +58,8 @@ public static ApplicationServerOptions forXsuaaService(String appId, String clie * the identity service * @return the application server options. */ - public static ApplicationServerOptions forService(Service service) { + public static ApplicationServerOptions forService(Service service, int jwksPort) { + tokenKeysPort = jwksPort; return switch (service) { case XSUAA -> forXsuaaService(SecurityTestRule.DEFAULT_APP_ID, SecurityTestRule.DEFAULT_CLIENT_ID); case IAS -> new ApplicationServerOptions(new IasTokenAuthenticator() @@ -72,6 +74,21 @@ public static ApplicationServerOptions forService(Service service) { } + public static ApplicationServerOptions forService(Service service) { + return switch (service) { + case XSUAA -> forXsuaaService(SecurityTestRule.DEFAULT_APP_ID, SecurityTestRule.DEFAULT_CLIENT_ID); + case IAS -> new ApplicationServerOptions(new IasTokenAuthenticator() + .withServiceConfiguration(OAuth2ServiceConfigurationBuilder.forService(Service.IAS) + .withClientId(SecurityTestRule.DEFAULT_CLIENT_ID) + .withUrl("http://localhost") + .withDomains("localhost") + .build())); + default -> + throw new UnsupportedOperationException("Identity Service " + service + " is not yet supported."); + }; + + } + /** * Use this method to configure a custom {@link TokenAuthenticator} that will be * used in the application server to authenticate the user via tokens retrieved @@ -107,10 +124,11 @@ public int getPort() { } private static OAuth2ServiceConfiguration createServiceConfiguration(String appId, String clientId) { + String portPath = tokenKeysPort != 0 ? ":" + tokenKeysPort : ""; return OAuth2ServiceConfigurationBuilder.forService(Service.XSUAA) .withClientId(clientId) .withProperty(ServiceConstants.XSUAA.APP_ID, appId) - .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, SecurityTestRule.DEFAULT_DOMAIN) + .withProperty(ServiceConstants.XSUAA.UAA_DOMAIN, SecurityTestRule.DEFAULT_UAA_DOMAIN + portPath) .build(); } diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTest.java b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTest.java index bfa9efda61..b70d672902 100644 --- a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTest.java +++ b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTest.java @@ -60,6 +60,7 @@ public class SecurityTest public static final String DEFAULT_APP_ID = "xsapp!t0815"; public static final String DEFAULT_CLIENT_ID = "sb-clientId!t0815"; public static final String DEFAULT_DOMAIN = "localhost"; + public static final String DEFAULT_UAA_DOMAIN = "http://localhost"; public static final String DEFAULT_URL = "http://localhost"; protected static final String LOCALHOST_PATTERN = "http://localhost:%d"; @@ -84,18 +85,18 @@ public SecurityTest(Service service) { this.service = service; this.keys = RSAKeys.generate(); this.wireMockServer = new WireMockServer(options().dynamicPort()); - this.applicationServerOptions = ApplicationServerOptions.forService(service); } @Override public SecurityTest useApplicationServer() { - return useApplicationServer(ApplicationServerOptions.forService(service)); + this.useApplicationServer = true; + return this; } @Override public SecurityTest useApplicationServer(ApplicationServerOptions applicationServerOptions) { this.applicationServerOptions = applicationServerOptions; - useApplicationServer = true; + this.useApplicationServer = true; return this; } @@ -266,18 +267,21 @@ String createDefaultOidcConfigurationResponse() throws IOException { * if the stub cannot be initialized */ public void setup() throws Exception { - if (useApplicationServer && (applicationServer == null || !applicationServer.isStarted())) { - startApplicationServer(); - } if (!wireMockServer.isRunning()) { wireMockServer.start(); } else { wireMockServer.resetAll(); } + if (useApplicationServer && (applicationServer == null || !applicationServer.isStarted())) { + if (applicationServerOptions == null){ + this.applicationServerOptions = ApplicationServerOptions.forService(service, wireMockServer.port()); + } + startApplicationServer(); + } // TODO return JSON Media type OAuth2ServiceEndpointsProvider endpointsProvider = new XsuaaDefaultEndpoints( String.format(LOCALHOST_PATTERN, wireMockServer.port()), null); - wireMockServer.stubFor(get(urlEqualTo(endpointsProvider.getJwksUri().getPath())) + wireMockServer.stubFor(get(urlPathEqualTo(endpointsProvider.getJwksUri().getPath())) .willReturn(aResponse().withBody(createDefaultTokenKeyResponse()) .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.value()))); wireMockServer.stubFor(get(urlEqualTo(DISCOVERY_ENDPOINT_DEFAULT)) diff --git a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java index 033326ccb3..1cba49f0f2 100644 --- a/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java +++ b/java-security-test/src/main/java/com/sap/cloud/security/test/SecurityTestRule.java @@ -25,6 +25,7 @@ public class SecurityTestRule extends ExternalResource public static final String DEFAULT_APP_ID = SecurityTest.DEFAULT_APP_ID; public static final String DEFAULT_CLIENT_ID = SecurityTest.DEFAULT_CLIENT_ID; public static final String DEFAULT_DOMAIN = SecurityTest.DEFAULT_DOMAIN; + public static final String DEFAULT_UAA_DOMAIN = SecurityTest.DEFAULT_UAA_DOMAIN; SecurityTest base; diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java index 1860a95e37..fd41dab078 100644 --- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java +++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilder.java @@ -27,7 +27,6 @@ import static com.sap.cloud.security.config.Service.IAS; import static com.sap.cloud.security.config.Service.XSUAA; -import static com.sap.cloud.security.config.ServiceConstants.XSUAA.UAA_DOMAIN; /** * Class used to build a token validator for an OAuth service configuration @@ -219,10 +218,6 @@ private List> createDefaultValidators() { OAuth2TokenKeyServiceWithCache tokenKeyServiceWithCache = getTokenKeyServiceWithCache(); Optional.ofNullable(tokenKeyCacheConfiguration).ifPresent(tokenKeyServiceWithCache::withCacheConfiguration); if (configuration.getService() == XSUAA) { - if (!configuration.isLegacyMode()) { - defaultValidators.add(new XsuaaJkuValidator(configuration.getProperty(UAA_DOMAIN))); - } - signatureValidator = new XsuaaJwtSignatureValidator(configuration, tokenKeyServiceWithCache, getOidcConfigurationServiceWithCache()); } else if (configuration.getService() == IAS) { if(configuration.getDomains() != null && !configuration.getDomains().isEmpty()) { diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJkuValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJkuValidator.java deleted file mode 100644 index dacd5f836c..0000000000 --- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJkuValidator.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2018-2023 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors - *

- * SPDX-License-Identifier: Apache-2.0 - */ -package com.sap.cloud.security.token.validation.validators; - -import com.sap.cloud.security.config.OAuth2ServiceConfiguration; -import com.sap.cloud.security.token.Token; -import com.sap.cloud.security.token.TokenHeader; -import com.sap.cloud.security.token.validation.ValidationResult; -import com.sap.cloud.security.token.validation.Validator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; - -import static com.sap.cloud.security.token.validation.ValidationResults.createInvalid; -import static com.sap.cloud.security.token.validation.ValidationResults.createValid; -import static com.sap.cloud.security.xsuaa.Assertions.assertHasText; - -/** - * Validates that the jwt access token is issued by a trust worthy identity - * service. In case of XSUAA does the token key url (jku JWT header parameter) - * must match the identity service domain. - */ -class XsuaaJkuValidator implements Validator { - private final String domain; - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - /** - * - * @param uaaDomain - * the domain of the identity service - * {@link OAuth2ServiceConfiguration#getProperty(String)} - */ - XsuaaJkuValidator(String uaaDomain) { - assertHasText(uaaDomain, "XsuaaJkuValidator requires uaaDomain."); - this.domain = uaaDomain; - } - - @Override - public ValidationResult validate(Token token) { - String tokenKeyUrl = token.getHeaderParameterAsString(TokenHeader.JWKS_URL); - URI jkuUri; - - if (tokenKeyUrl == null || tokenKeyUrl.trim().isEmpty()) { - return createInvalid( - "Issuer validation can not be performed because Jwt token does not contain 'jku' header parameter."); - } - try { - jkuUri = URI.create(tokenKeyUrl); - } catch (IllegalArgumentException e) { - return createInvalid( - "Issuer validation can not be performed because Jwt token does not contain a valid uri as 'jku' header parameter."); - } - if (!matchesTokenKeyUrlDomain(jkuUri)) { - return createInvalid( - "Issuer is not trusted because 'jku' '{}' does not match uaa domain '{}' of the identity service.", - jkuUri, domain); - } - if (!matchesTokenKeyEndpoint(jkuUri)) { - return createInvalid( - "Jwt token does not contain a valid 'jku' header parameter.", - jkuUri, domain); - } - return createValid(); - } - - private boolean matchesTokenKeyUrlDomain(URI jkuUri) { - return jkuUri.getHost() != null && jkuUri.getHost().endsWith(domain); - } - - private boolean matchesTokenKeyEndpoint(URI jkuUri) { - return jkuUri.getPath().endsWith("token_keys") && jkuUri.getQuery() == null && jkuUri.getFragment() == null; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - XsuaaJkuValidator that = (XsuaaJkuValidator) o; - - return domain.equals(that.domain); - } - - @Override - public int hashCode() { - return domain.hashCode(); - } -} diff --git a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidator.java b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidator.java index f1a800209a..82ef675113 100644 --- a/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidator.java +++ b/java-security/src/main/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidator.java @@ -13,7 +13,9 @@ import java.util.Collections; import java.util.Map; -import static com.sap.cloud.security.token.validation.validators.JsonWebKeyConstants.*; +import static com.sap.cloud.security.config.ServiceConstants.XSUAA.UAA_DOMAIN; +import static com.sap.cloud.security.token.validation.validators.JsonWebKeyConstants.KEY_ID_VALUE_LEGACY; +import static com.sap.cloud.security.token.validation.validators.JsonWebKeyConstants.KID_PARAMETER_NAME; /** * Jwt Signature validator for Access tokens issued by Xsuaa service @@ -54,12 +56,19 @@ private PublicKey fetchPublicKey(Token token, JwtSignatureAlgorithm algorithm) t throw new IllegalArgumentException("Token does not contain the mandatory " + KID_PARAMETER_NAME + " header."); } - String jwksUri = configuration.isLegacyMode() ? configuration.getUrl() + "/token_keys" : token.getHeaderParameterAsString(JKU_PARAMETER_NAME); - if (jwksUri == null) { - throw new IllegalArgumentException("Token does not contain the mandatory " + JKU_PARAMETER_NAME + " header."); - } - + String zidQueryParam = composeZidQueryParameter(token); + String jwksUri = configuration.isLegacyMode() ? configuration.getUrl() + "/token_keys" : configuration.getProperty(UAA_DOMAIN) + "/token_keys" + zidQueryParam; + URI uri = URI.create(jwksUri); + uri = uri.isAbsolute() ? uri : URI.create("https://" + jwksUri); Map params = Collections.singletonMap(HttpHeaders.X_ZID, token.getAppTid()); - return tokenKeyService.getPublicKey(algorithm, keyId, URI.create(jwksUri), params); + return tokenKeyService.getPublicKey(algorithm, keyId, uri, params); + } + + private String composeZidQueryParameter(Token token) { + String zid = token.getAppTid(); + if (zid != null && !zid.isBlank()){ + return "?zid=" + zid; + } + return ""; } } diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java index 290941891f..cbfea85c45 100644 --- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java +++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/JwtValidatorBuilderTest.java @@ -58,8 +58,7 @@ public void sameServiceConfiguration_getSameInstance() { JwtValidatorBuilder builder_1 = JwtValidatorBuilder.getInstance(configuration); JwtValidatorBuilder builder_2 = JwtValidatorBuilder.getInstance(configuration); JwtValidatorBuilder builder_3 = JwtValidatorBuilder.getInstance(xsuaaConfigBuilder.build()); - assertThat(builder_1).isSameAs(builder_2); - assertThat(builder_1).isSameAs(builder_3); + assertThat(builder_1).isSameAs(builder_2).isSameAs(builder_3); } @Test @@ -79,10 +78,9 @@ public void build_containsAllDefaultValidators() { .getValidators(); assertThat(validators) - .hasSize(4) + .hasSize(3) .hasAtLeastOneElementOfType(JwtTimestampValidator.class) .hasAtLeastOneElementOfType(JwtAudienceValidator.class) - .hasAtLeastOneElementOfType(XsuaaJkuValidator.class) .hasAtLeastOneElementOfType(JwtSignatureValidator.class); } @@ -97,8 +95,7 @@ public void buildLegacy_containsAllDefaultValidators() { .hasSize(3) .hasAtLeastOneElementOfType(JwtTimestampValidator.class) .hasAtLeastOneElementOfType(JwtAudienceValidator.class) - .hasAtLeastOneElementOfType(JwtSignatureValidator.class) - .doesNotHaveAnyElementsOfTypes(XsuaaJkuValidator.class); + .hasAtLeastOneElementOfType(JwtSignatureValidator.class); } @Test diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJkuValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJkuValidatorTest.java deleted file mode 100644 index 944f2eff47..0000000000 --- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJkuValidatorTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2018-2023 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors - *

- * SPDX-License-Identifier: Apache-2.0 - */ -package com.sap.cloud.security.token.validation.validators; - -import com.sap.cloud.security.token.Token; -import com.sap.cloud.security.token.validation.ValidationResult; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -import static com.sap.cloud.security.token.TokenHeader.JWKS_URL; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.hamcrest.CoreMatchers.*; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.Mockito.when; - -public class XsuaaJkuValidatorTest { - private XsuaaJkuValidator cut; - private Token token; - - @Before - public void setup() { - cut = new XsuaaJkuValidator("myauth.ondemand.com"); - token = Mockito.mock(Token.class); - } - - @Test - public void constructor_throwsOnNullValues() { - assertThatThrownBy(() -> { - new XsuaaJkuValidator(null); - }).isInstanceOf(IllegalArgumentException.class).hasMessageContainingAll("XsuaaJkuValidator", "uaaDomain"); - - assertThatThrownBy(() -> { - new XsuaaJkuValidator(" "); - }).isInstanceOf(IllegalArgumentException.class).hasMessageContainingAll("XsuaaJkuValidator", "uaaDomain"); - } - - @Test - public void jwksMatchesIdentityServiceDomain() { - when(token.getHeaderParameterAsString(JWKS_URL)).thenReturn("https://subdomain.myauth.ondemand.com/token_keys"); - assertThat(cut.validate(token).isValid(), is(true)); - } - - @Test - public void validationFails_whenJwksDoesNotMatchIdentityServiceDomain() { - when(token.getHeaderParameterAsString(JWKS_URL)).thenReturn("https://subdomain.any.ondemand.com"); - ValidationResult validationResult = cut.validate(token); - assertThat(validationResult.isErroneous(), is(true)); - assertThat(validationResult.getErrorDescription(), startsWith( - "Issuer is not trusted because 'jku' 'https://subdomain.any.ondemand.com' does not match uaa domain 'myauth.ondemand.com' of the identity service.")); - } - - @Test - public void validationFails_whenJwksIsEmpty() { - when(token.getHeaderParameterAsString(JWKS_URL)).thenReturn(" "); - ValidationResult validationResult = cut.validate(token); - assertThat(validationResult.isErroneous(), is(true)); - assertThat(validationResult.getErrorDescription(), startsWith( - "Issuer validation can not be performed because Jwt token does not contain 'jku' header parameter.")); - } - - @Test - public void validationFails_whenJwksIsNull() { - when(token.getHeaderParameterAsString(JWKS_URL)).thenReturn(null); - ValidationResult validationResult = cut.validate(token); - assertThat(validationResult.isErroneous(), is(true)); - assertThat(validationResult.getErrorDescription(), startsWith( - "Issuer validation can not be performed because Jwt token does not contain 'jku' header parameter.")); - } - - @Test - public void validationFails_whenJwksIsNotAValidUri() { - when(token.getHeaderParameterAsString(JWKS_URL)).thenReturn("\0://myauth.com"); - ValidationResult validationResult = cut.validate(token); - assertThat(validationResult.isErroneous(), is(true)); - assertThat(validationResult.getErrorDescription(), - containsString("Jwt token does not contain a valid uri as 'jku' header parameter")); - } - - @Test - public void validationFails_whenJwksDoesNotContainAValidPath() { - when(token.getHeaderParameterAsString(JWKS_URL)) - .thenReturn("https://subdomain.myauth.ondemand.com/wrong_endpoint"); - ValidationResult validationResult = cut.validate(token); - assertThat(validationResult.isErroneous(), is(true)); - assertThat(validationResult.getErrorDescription(), - containsString("Jwt token does not contain a valid 'jku' header parameter")); - } - - @Test - public void validationFails_whenJwksContainsQueryParameters() { - when(token.getHeaderParameterAsString(JWKS_URL)) - .thenReturn("https://subdomain.myauth.ondemand.com/token_keys?a=b"); - ValidationResult validationResult = cut.validate(token); - assertThat(validationResult.isErroneous(), is(true)); - assertThat(validationResult.getErrorDescription(), - containsString("Jwt token does not contain a valid 'jku' header parameter")); - } - - @Test - public void validationFails_whenJwksContainsFragment() { - when(token.getHeaderParameterAsString(JWKS_URL)) - .thenReturn("https://subdomain.myauth.ondemand.com/token_keys#token_keys"); - ValidationResult validationResult = cut.validate(token); - assertThat(validationResult.isErroneous(), is(true)); - assertThat(validationResult.getErrorDescription(), - containsString("Jwt token does not contain a valid 'jku' header parameter")); - } - - @Test - public void validationFails_whenJkuHasNonTrustedHost() { - when(token.getHeaderParameterAsString(JWKS_URL)) - .thenReturn("http://myauth.ondemand.com\\@malicious.ondemand.com/token_keys"); - assertThat(cut.validate(token).isValid(), is(false)); - when(token.getHeaderParameterAsString(JWKS_URL)) - .thenReturn("http://myauth.ondemand.com@malicious.ondemand.com/token_keys"); - assertThat(cut.validate(token).isValid(), is(false)); - when(token.getHeaderParameterAsString(JWKS_URL)) - .thenReturn("http://malicious.ondemand.com/token_keys///myauth.ondemand.com/token_keys"); - assertThat(cut.validate(token).isValid(), is(false)); - } -} diff --git a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidatorTest.java b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidatorTest.java index f31807dcb5..8eeb54832e 100644 --- a/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidatorTest.java +++ b/java-security/src/test/java/com/sap/cloud/security/token/validation/validators/XsuaaJwtSignatureValidatorTest.java @@ -7,7 +7,6 @@ import com.sap.cloud.security.config.OAuth2ServiceConfiguration; import com.sap.cloud.security.config.Service; -import com.sap.cloud.security.token.SapIdToken; import com.sap.cloud.security.token.Token; import com.sap.cloud.security.token.XsuaaToken; import com.sap.cloud.security.token.validation.ValidationResult; @@ -23,11 +22,11 @@ import java.net.URI; import java.util.Map; +import static com.sap.cloud.security.config.ServiceConstants.XSUAA.UAA_DOMAIN; import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; public class XsuaaJwtSignatureValidatorTest { @@ -56,11 +55,12 @@ public void setup() throws IOException { mockConfiguration = Mockito.mock(OAuth2ServiceConfiguration.class); when(mockConfiguration.getService()).thenReturn(Service.XSUAA); + when(mockConfiguration.getProperty(UAA_DOMAIN)).thenReturn("authentication.stagingaws.hanavlab.ondemand.com"); tokenKeyServiceMock = Mockito.mock(OAuth2TokenKeyService.class); when(tokenKeyServiceMock - .retrieveTokenKeys(eq(URI.create("https://authentication.stagingaws.hanavlab.ondemand.com/token_keys")), - eq(Map.of(HttpHeaders.X_ZID, "uaa")))) + .retrieveTokenKeys(URI.create("https://authentication.stagingaws.hanavlab.ondemand.com/token_keys?zid=uaa"), + Map.of(HttpHeaders.X_ZID, "uaa"))) .thenReturn(IOUtils.resourceToString("/jsonWebTokenKeys.json", UTF_8)); cut = new XsuaaJwtSignatureValidator( @@ -75,32 +75,20 @@ public void xsuaa_RSASignatureMatchesJWKS() { assertThat(cut.validate(xsuaaToken).isValid(), is(true)); } - @Test - public void validationFails_whenNoJkuHeaderButIssuerIsGiven() throws IOException { - /** - * - * Header -------- { "alg": "RS256" } Payload -------- { "iss": - * "https://application.myauth.com" } - */ - Token tokenWithoutJkuButIssuer = new SapIdToken(IOUtils.resourceToString("/iasOidcTokenRSA256.txt", UTF_8)); - ValidationResult result = cut.validate(tokenWithoutJkuButIssuer); - assertThat(result.isErroneous(), is(true)); - assertThat(result.getErrorDescription(), containsString("Token does not contain the mandatory " + JsonWebKeyConstants.JKU_PARAMETER_NAME + " header")); - } - @Test public void generatedToken_SignatureMatchesVerificationkey() { when(mockConfiguration.hasProperty("verificationkey")).thenReturn(true); when(mockConfiguration.getProperty("verificationkey")).thenReturn( - "-----BEGIN PUBLIC KEY-----\n" + - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1QaZzMjtEfHdimrHP3/\n" + - "2Yr+1z685eiOUlwybRVG9i8wsgOUh+PUGuQL8hgulLZWXU5MbwBLTECAEMQbcRTN\n" + - "VTolkq4i67EP6JesHJIFADbK1Ni0KuMcPuiyOLvDKiDEMnYG1XP3X3WCNfsCVT9Y\n" + - "oU+lWIrZr/ZsIvQri8jczr4RkynbTBsPaAOygPUlipqDrpadMO1momNCbea/o6GP\n" + - "n38LxEw609ItfgDGhL6f/yVid5pFzZQWb+9l6mCuJww0hnhO6gt6Rv98OWDty9G0\n" + - "frWAPyEfuIW9B+mR/2vGhyU9IbbWpvFXiy9RVbbsM538TCjd5JF2dJvxy24addC4\n" + - "oQIDAQAB\n" + - "-----END PUBLIC KEY-----"); + """ + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm1QaZzMjtEfHdimrHP3/ + 2Yr+1z685eiOUlwybRVG9i8wsgOUh+PUGuQL8hgulLZWXU5MbwBLTECAEMQbcRTN + VTolkq4i67EP6JesHJIFADbK1Ni0KuMcPuiyOLvDKiDEMnYG1XP3X3WCNfsCVT9Y + oU+lWIrZr/ZsIvQri8jczr4RkynbTBsPaAOygPUlipqDrpadMO1momNCbea/o6GP + n38LxEw609ItfgDGhL6f/yVid5pFzZQWb+9l6mCuJww0hnhO6gt6Rv98OWDty9G0 + frWAPyEfuIW9B+mR/2vGhyU9IbbWpvFXiy9RVbbsM538TCjd5JF2dJvxy24addC4 + oQIDAQAB + -----END PUBLIC KEY-----"""); assertThat(cut.validate(xsuaaTokenSignedWithVerificationKey).isValid(), is(true)); } diff --git a/samples/spring-security-basic-auth/src/test/java/sample/spring/xsuaa/SecurityConfigurationTest.java b/samples/spring-security-basic-auth/src/test/java/sample/spring/xsuaa/SecurityConfigurationTest.java index 1f931a3fb6..c54eca05bd 100644 --- a/samples/spring-security-basic-auth/src/test/java/sample/spring/xsuaa/SecurityConfigurationTest.java +++ b/samples/spring-security-basic-auth/src/test/java/sample/spring/xsuaa/SecurityConfigurationTest.java @@ -6,6 +6,7 @@ package sample.spring.xsuaa; import com.sap.cloud.security.config.ClientIdentity; +import com.sap.cloud.security.config.Service; import com.sap.cloud.security.test.api.SecurityTestContext; import com.sap.cloud.security.test.extension.XsuaaExtension; import com.sap.cloud.security.token.Token; @@ -23,6 +24,7 @@ import org.springframework.context.annotation.Import; import org.springframework.test.web.servlet.MockMvc; import sample.spring.xsuaa.config.TokenBrokerTestConfiguration; +import sample.spring.xsuaa.config.XsuaaExtensionFixedPort; import java.net.URI; import java.util.Collection; @@ -48,7 +50,7 @@ @SpringBootTest @AutoConfigureMockMvc @Import(TokenBrokerTestConfiguration.class) -@ExtendWith(XsuaaExtension.class) +@ExtendWith(XsuaaExtensionFixedPort.class) public class SecurityConfigurationTest { /** users for which the TokenBrokerResolver returns stubbed results. */ private enum User {MISSING_SCOPES, WRONG_SCOPE, VALID_SCOPE, VALID_SCOPE_ON_OTHER_ZONE} @@ -60,6 +62,11 @@ private enum User {MISSING_SCOPES, WRONG_SCOPE, VALID_SCOPE, VALID_SCOPE_ON_OTHE @MockBean private XsuaaOAuth2TokenService xsuaaTokenService; + + static { + XsuaaExtension.forService(Service.XSUAA).setPort(2222); + } + /** Configures the xsuaaTokenService with stub responses. * For each user, a generated access token is returned if the user's name, password and zone are correct. * For each user, an exception is thrown when a wrong password is used. diff --git a/samples/spring-security-basic-auth/src/test/java/sample/spring/xsuaa/config/XsuaaExtensionFixedPort.java b/samples/spring-security-basic-auth/src/test/java/sample/spring/xsuaa/config/XsuaaExtensionFixedPort.java new file mode 100644 index 0000000000..80b559ccdb --- /dev/null +++ b/samples/spring-security-basic-auth/src/test/java/sample/spring/xsuaa/config/XsuaaExtensionFixedPort.java @@ -0,0 +1,15 @@ +/** + * SPDX-FileCopyrightText: 2018-2023 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package sample.spring.xsuaa.config; + +import com.sap.cloud.security.test.extension.XsuaaExtension; + +public class XsuaaExtensionFixedPort extends XsuaaExtension { + + public XsuaaExtensionFixedPort() { + super(); + this.setPort(2222); + } +} diff --git a/samples/spring-security-basic-auth/src/test/resources/application.yml b/samples/spring-security-basic-auth/src/test/resources/application.yml index a02080233b..f38f57220a 100644 --- a/samples/spring-security-basic-auth/src/test/resources/application.yml +++ b/samples/spring-security-basic-auth/src/test/resources/application.yml @@ -3,7 +3,7 @@ sap: services: xsuaa: # default configuration of the test XSUAA server from SecurityTest xsappname: xsapp!t0815 - uaadomain: localhost + uaadomain: http://localhost:2222 clientid: sb-clientId!t0815 url: http://localhost spring: diff --git a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/TestControllerTest.java b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/TestControllerTest.java index 336fcf4da0..c77841d39d 100644 --- a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/TestControllerTest.java +++ b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/TestControllerTest.java @@ -35,7 +35,7 @@ public class TestControllerTest { private String jwtIas; @ClassRule - public static SecurityTestRule ruleXsuaa = SecurityTestRule.getInstance(Service.XSUAA); + public static SecurityTestRule ruleXsuaa = SecurityTestRule.getInstance(Service.XSUAA).setPort(2223); @ClassRule public static SecurityTestRule ruleIas = SecurityTestRule.getInstance(Service.IAS); diff --git a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerXsuaaTest.java b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerXsuaaTest.java index 50e6364c03..f93c8216ec 100644 --- a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerXsuaaTest.java +++ b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/TestControllerXsuaaTest.java @@ -6,7 +6,6 @@ package sample.spring.security.junitjupiter; import com.sap.cloud.security.test.api.SecurityTestContext; -import com.sap.cloud.security.test.extension.XsuaaExtension; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -23,7 +22,7 @@ @SpringBootTest @AutoConfigureMockMvc -@ExtendWith(XsuaaExtension.class) +@ExtendWith(XsuaaExtensionFixedPort.class) @ActiveProfiles("multixsuaa") // properties are provided with /resources/application-multixsuaa.yml class TestControllerXsuaaTest { diff --git a/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/XsuaaExtensionFixedPort.java b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/XsuaaExtensionFixedPort.java new file mode 100644 index 0000000000..2b808a37b2 --- /dev/null +++ b/samples/spring-security-hybrid-usage/src/test/java/sample/spring/security/junitjupiter/XsuaaExtensionFixedPort.java @@ -0,0 +1,15 @@ +/** + * SPDX-FileCopyrightText: 2018-2023 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package sample.spring.security.junitjupiter; + +import com.sap.cloud.security.test.extension.XsuaaExtension; + +public class XsuaaExtensionFixedPort extends XsuaaExtension { + + public XsuaaExtensionFixedPort() { + super(); + this.setPort(2223); + } +} diff --git a/samples/spring-security-hybrid-usage/src/test/resources/application-multixsuaa.yml b/samples/spring-security-hybrid-usage/src/test/resources/application-multixsuaa.yml index 9fa74bec18..2729a65a65 100644 --- a/samples/spring-security-hybrid-usage/src/test/resources/application-multixsuaa.yml +++ b/samples/spring-security-hybrid-usage/src/test/resources/application-multixsuaa.yml @@ -6,7 +6,7 @@ sap: services: xsuaa[0]: xsappname: xsapp!t0815 - uaadomain: localhost + uaadomain: http://localhost:2223 clientid: sb-clientId!t0815 clientsecret: pwd url: http://localhost diff --git a/samples/spring-security-hybrid-usage/src/test/resources/application.yml b/samples/spring-security-hybrid-usage/src/test/resources/application.yml index 88652943b3..20e80498bd 100644 --- a/samples/spring-security-hybrid-usage/src/test/resources/application.yml +++ b/samples/spring-security-hybrid-usage/src/test/resources/application.yml @@ -6,7 +6,7 @@ sap: services: xsuaa: xsappname: xsapp!t0815 - uaadomain: localhost + uaadomain: http://localhost:2223 clientid: sb-clientId!t0815 clientsecret: pwd url: http://localhost diff --git a/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/TestControllerTest.java b/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/TestControllerTest.java index c4fc8ede8c..baaa2cae87 100644 --- a/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/TestControllerTest.java +++ b/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/TestControllerTest.java @@ -21,6 +21,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.RequestPostProcessor; +import static com.sap.cloud.security.test.SecurityTest.*; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -28,7 +29,10 @@ @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc -@TestPropertySource(properties = {"xsuaa.uaadomain=localhost", "xsuaa.xsappname=xsapp!t0815", "xsuaa.clientid=sb-clientId!t0815" }) +@TestPropertySource(properties = { + "xsuaa.uaadomain=" + DEFAULT_UAA_DOMAIN + ":2225", + "xsuaa.xsappname=" + DEFAULT_APP_ID, + "xsuaa.clientid=" + DEFAULT_CLIENT_ID }) public class TestControllerTest { @Autowired @@ -39,7 +43,7 @@ public class TestControllerTest { private String jwtAdmin; @ClassRule - public static SecurityTestRule rule = SecurityTestRule.getInstance(Service.XSUAA); + public static SecurityTestRule rule = SecurityTestRule.getInstance(Service.XSUAA).setPort(2225); @Before public void setUp() { diff --git a/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/TestControllerTest.java b/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/TestControllerTest.java index 5792478ad3..cd9b165601 100644 --- a/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/TestControllerTest.java +++ b/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/TestControllerTest.java @@ -6,7 +6,6 @@ package sample.spring.xsuaa.junitjupiter; import com.sap.cloud.security.test.api.SecurityTestContext; -import com.sap.cloud.security.test.extension.XsuaaExtension; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -27,10 +26,10 @@ @SpringBootTest @AutoConfigureMockMvc @TestPropertySource(properties = { - "xsuaa.uaadomain=" + DEFAULT_DOMAIN, + "xsuaa.uaadomain=" + DEFAULT_UAA_DOMAIN + ":2224", "xsuaa.xsappname=" + DEFAULT_APP_ID, "xsuaa.clientid=" + DEFAULT_CLIENT_ID }) -@ExtendWith(XsuaaExtension.class) +@ExtendWith(XsuaaExtensionFixedPort.class) class TestControllerTest { @Autowired diff --git a/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/XsuaaExtensionFixedPort.java b/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/XsuaaExtensionFixedPort.java new file mode 100644 index 0000000000..3865407716 --- /dev/null +++ b/samples/spring-security-xsuaa-usage/src/test/java/sample/spring/xsuaa/junitjupiter/XsuaaExtensionFixedPort.java @@ -0,0 +1,15 @@ +/** + * SPDX-FileCopyrightText: 2018-2023 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package sample.spring.xsuaa.junitjupiter; + +import com.sap.cloud.security.test.extension.XsuaaExtension; + +public class XsuaaExtensionFixedPort extends XsuaaExtension { + + public XsuaaExtensionFixedPort() { + super(); + this.setPort(2224); + } +} diff --git a/spring-xsuaa-it/src/main/java/com/sap/cloud/security/xsuaa/mock/JWTUtil.java b/spring-xsuaa-it/src/main/java/com/sap/cloud/security/xsuaa/mock/JWTUtil.java index 025a928ccd..5aa71925fa 100644 --- a/spring-xsuaa-it/src/main/java/com/sap/cloud/security/xsuaa/mock/JWTUtil.java +++ b/spring-xsuaa-it/src/main/java/com/sap/cloud/security/xsuaa/mock/JWTUtil.java @@ -25,4 +25,10 @@ public static String createJWT(String pathToTemplate, String subdomain, String k return jwtGenerator.createFromTemplate(pathToTemplate).getTokenValue(); } + public static String createJWT(String pathToTemplate, String subdomain, String zid, String keyId) throws IOException { + JwtGenerator jwtGenerator = new JwtGenerator("sb-java-hello-world", subdomain, zid) + .setJwtHeaderKeyId(keyId != null ? keyId : "legacy-token-key-" + subdomain); + return jwtGenerator.createFromTemplate(pathToTemplate).getTokenValue(); + } + } diff --git a/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java b/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java index b087a5ebc0..4a321e2010 100644 --- a/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java +++ b/spring-xsuaa-it/src/main/java/testservice/api/v1/TestController.java @@ -63,7 +63,7 @@ String message(@AuthenticationPrincipal Token token) { // email assertEquals("max@example.com", token.getEmail()); // zone - assertTrue(token.getZoneId().endsWith("domain-id")); + assertTrue(token.getZoneId().endsWith("tenant")); // ext attr assertEquals("domain\\group1", token.getAdditionalAuthAttribute("external_group")); assertEquals("abcd1234", token.getAdditionalAuthAttribute("external_id")); diff --git a/spring-xsuaa-it/src/test/java/testservice/api/MockXsuaaServerConfiguration.java b/spring-xsuaa-it/src/test/java/testservice/api/MockXsuaaServerConfiguration.java index c39c490c7b..ffcc39e50a 100644 --- a/spring-xsuaa-it/src/test/java/testservice/api/MockXsuaaServerConfiguration.java +++ b/spring-xsuaa-it/src/test/java/testservice/api/MockXsuaaServerConfiguration.java @@ -7,7 +7,7 @@ import java.io.IOException; @TestPropertySource(properties = { "xsuaa.xsappname=java-hello-world", "xsuaa.clientid=sb-java-hello-world", - "xsuaa.url=http://localhost:33195", "xsuaa.uaadomain=localhost" }) + "xsuaa.url=http://localhost:33195", "xsuaa.uaadomain=http://localhost:33195" }) public class MockXsuaaServerConfiguration { private static final int DEFAULT_PORT = 33195; private static MockWebServer server; diff --git a/spring-xsuaa-it/src/test/java/testservice/api/XsuaaRequestDispatcher.java b/spring-xsuaa-it/src/test/java/testservice/api/XsuaaRequestDispatcher.java index d30bde044c..dc1d54fd26 100644 --- a/spring-xsuaa-it/src/test/java/testservice/api/XsuaaRequestDispatcher.java +++ b/spring-xsuaa-it/src/test/java/testservice/api/XsuaaRequestDispatcher.java @@ -27,16 +27,15 @@ public class XsuaaRequestDispatcher extends Dispatcher { @Override public MockResponse dispatch(RecordedRequest request) { // mock JWKS endpoints - if ("/testdomain/token_keys".equals(request.getPath())) { - String subdomain = "testdomain"; - return getTokenKeyForKeyId(PATH_TESTDOMAIN_TOKEN_KEYS, "legacy-token-key-" + subdomain); + if ("/token_keys?zid=tenant".equals(request.getPath())) { + return getTokenKeyForKeyId(PATH_TESTDOMAIN_TOKEN_KEYS, "legacy-token-key-testdomain"); } - if ("/otherdomain/token_keys".equals(request.getPath())) { + if ("/token_keys?zid=othertenant".equals(request.getPath())) { return getResponseFromFile(PATH_OTHER_DOMAIN_TOKEN_KEYS, HttpStatus.OK); } - if (request.getPath().endsWith("/token_keys")) { + if (request.getPath().contains("/token_keys")) { return getTokenKeyForKeyId(PATH_TESTDOMAIN_TOKEN_KEYS, "legacy-token-key"); } diff --git a/spring-xsuaa-it/src/test/java/testservice/api/nohttp/XsuaaJwtDecoderTest.java b/spring-xsuaa-it/src/test/java/testservice/api/nohttp/XsuaaJwtDecoderTest.java index 0f2b2b7484..b573426bfa 100644 --- a/spring-xsuaa-it/src/test/java/testservice/api/nohttp/XsuaaJwtDecoderTest.java +++ b/spring-xsuaa-it/src/test/java/testservice/api/nohttp/XsuaaJwtDecoderTest.java @@ -61,7 +61,7 @@ void postValidationActionIsExecutedIfSuccess() { @Test void postValidationActionIsNotExecutedIfFail() { - String jwt = new JwtGenerator(clientId, "subdomain").deriveAudiences(true) + String jwt = new JwtGenerator(clientId, "subdomain", "tenant").deriveAudiences(true) .setJwtHeaderKeyId("legacy-token-key").setJku(null).getToken().getTokenValue(); try { jwtDecoderWithPostAction.decode(jwt); diff --git a/spring-xsuaa-it/src/test/java/testservice/api/v1/XsuaaTokenValidationTest.java b/spring-xsuaa-it/src/test/java/testservice/api/v1/XsuaaTokenValidationTest.java index e56a6a1be3..369a17e55b 100644 --- a/spring-xsuaa-it/src/test/java/testservice/api/v1/XsuaaTokenValidationTest.java +++ b/spring-xsuaa-it/src/test/java/testservice/api/v1/XsuaaTokenValidationTest.java @@ -40,21 +40,21 @@ private static BearerTokenRequestPostProcessor bearerToken(String token) { @Test void testToken_testdomain() throws Exception { this.mvc.perform(get("/user").with(bearerToken(JWTUtil.createJWT("/saml.txt", - "testdomain")))) + "testdomain", "tenant", null)))) .andExpect(status().isOk()).andExpect(content().string(containsString("user:Mustermann"))); } @Test void testToken_otherdomain() throws Exception { this.mvc.perform(get("/user").with(bearerToken(JWTUtil.createJWT("/saml.txt", - "otherdomain")))) + "otherdomain", "othertenant", null)))) .andExpect(status().isOk()).andExpect(content().string(containsString("user:Mustermann"))); } @Test void test_Scope() throws Exception { this.mvc.perform(get("/scope").with(bearerToken(JWTUtil.createJWT("/saml.txt", - "otherdomain")))) + "otherdomain", "othertenant", null)))) .andExpect(status().isOk()); } diff --git a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java index eb5156b972..1bf2e36e19 100644 --- a/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java +++ b/spring-xsuaa/src/main/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoder.java @@ -8,7 +8,9 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTParser; +import com.sap.cloud.security.config.ServiceConstants; import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration; +import com.sap.cloud.security.xsuaa.token.TokenClaims; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,8 +22,7 @@ import org.springframework.util.Assert; import org.springframework.web.client.RestOperations; -import java.net.URI; -import java.net.URISyntaxException; +import javax.annotation.Nullable; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPublicKey; @@ -109,10 +110,9 @@ public void setRestOperations(RestOperations restOperations) { private Jwt verifyToken(JWT jwt) { try { - String jku = tokenInfoExtractor.getJku(jwt); String kid = tokenInfoExtractor.getKid(jwt); String uaaDomain = tokenInfoExtractor.getUaaDomain(jwt); - return verifyToken(jwt.getParsedString(), jku, kid, uaaDomain); + return verifyToken(jwt.getParsedString(), kid, uaaDomain, getZid(jwt)); } catch (BadJwtException e) { if (e.getMessage().contains("Couldn't retrieve remote JWK set") || e.getMessage().contains("Cannot verify with online token key, uaadomain is")) { @@ -124,11 +124,26 @@ private Jwt verifyToken(JWT jwt) { } } - private Jwt verifyToken(String token, String jku, String kid, String uaaDomain) { + @Nullable + private static String getZid(JWT jwt) { + String zid; try { - canVerifyWithKey(jku, kid, uaaDomain); - validateJku(jku, uaaDomain); - return verifyWithKey(token, jku, kid); + zid = jwt.getJWTClaimsSet().getStringClaim( + TokenClaims.CLAIM_ZONE_ID); + + } catch (ParseException e) { + zid =null; + } + if (zid != null && zid.isBlank()){ + zid = null; + } + return zid; + } + + private Jwt verifyToken(String token, String kid, String uaaDomain, String zid) { + try { + canVerifyWithKey(kid, uaaDomain); + return verifyWithKey(token, composeJku(uaaDomain, zid), kid); } catch (JwtValidationException ex) { throw ex; } catch (JwtException ex) { @@ -136,39 +151,28 @@ private Jwt verifyToken(String token, String jku, String kid, String uaaDomain) } } - private void canVerifyWithKey(String jku, String kid, String uaadomain) { - if (jku != null && kid != null && uaadomain != null) { + private void canVerifyWithKey(String kid, String uaadomain) { + if (kid != null && uaadomain != null) { return; } List nullParams = new ArrayList<>(); - if (jku == null) - nullParams.add("jku"); if (kid == null) - nullParams.add("kid"); + nullParams.add(CLAIM_KID); if (uaadomain == null) - nullParams.add("uaadomain"); + nullParams.add(ServiceConstants.XSUAA.UAA_DOMAIN); throw new BadJwtException(String.format("Cannot verify with online token key, %s is null", String.join(", ", nullParams))); } - private void validateJku(String jku, String uaadomain) { - try { - URI jkuUri = new URI(jku); - if (jkuUri.getHost() == null) { - throw new BadJwtException("JKU of token is not valid"); - } else if (!jkuUri.getHost().endsWith(uaadomain)) { - logger.warn("Error: Do not trust jku '{}' because it does not match uaa domain '{}'.", - jku, uaadomain); - throw new BadJwtException("Do not trust 'jku' token header."); - } else if (!jkuUri.getPath().endsWith("token_keys") || hasText(jkuUri.getQuery()) - || hasText(jkuUri.getFragment())) { - logger.warn("Error: Do not trust jku '{}' because it contains invalid path, query or fragment.", jku); - throw new BadJwtException("Jwt token does not contain a valid 'jku' header parameter: " + jkuUri); - } - } catch (URISyntaxException e) { - throw new BadJwtException("JKU of token header is not valid"); + private String composeJku(String uaaDomain, String zid) { + String zidQueryParam = zid != null ? "?zid=" + zid : ""; + + // uaaDomain in configuration is always without a schema, but for testing purpose http schema can be used + if (uaaDomain.startsWith("http://")){ + return uaaDomain + "/token_keys" + zidQueryParam; } + return "https://" + uaaDomain + "/token_keys" + zidQueryParam; } @java.lang.SuppressWarnings("squid:S2259") diff --git a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderTest.java b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderTest.java index 506438f8c1..5f2a881bda 100644 --- a/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderTest.java +++ b/spring-xsuaa/src/test/java/com/sap/cloud/security/xsuaa/token/authentication/XsuaaJwtDecoderTest.java @@ -5,7 +5,6 @@ */ package com.sap.cloud.security.xsuaa.token.authentication; -import com.nimbusds.jwt.JWT; import com.sap.cloud.security.xsuaa.XsuaaCredentials; import com.sap.cloud.security.xsuaa.XsuaaServiceConfiguration; import com.sap.cloud.security.xsuaa.XsuaaServiceConfigurationCustom; @@ -125,89 +124,7 @@ public void decode_withNonMatchingVerificationKey_throwsException() { final JwtDecoder cut = new XsuaaJwtDecoderBuilder(configuration).build(); assertThatThrownBy(() -> cut.decode(ccToken)).isInstanceOf(JwtException.class) - .hasMessageContaining("Cannot verify with online token key, jku, kid, uaadomain is null"); + .hasMessageContaining("Cannot verify with online token key, kid, uaadomain is null"); } - @Test - public void decode_whenJwksContainsInvalidJwksDomain_throwsException() throws IOException { - String token = IOUtils.resourceToString("/token_user.txt", StandardCharsets.UTF_8); - XsuaaJwtDecoder cut = (XsuaaJwtDecoder) new XsuaaJwtDecoderBuilder(configuration).build(); - - cut.setTokenInfoExtractor(new TokenInfoExtractorImpl("https://subdomain.wrongoauth.ondemand.com/token_keys")); - assertThatThrownBy(() -> cut.decode(token)).isInstanceOf(JwtException.class) - .hasMessageContaining("JWT verification failed: Do not trust 'jku' token header"); - - cut.setTokenInfoExtractor( - new TokenInfoExtractorImpl("http://myauth.ondemand.com@malicious.ondemand.com/token_keys")); - assertThatThrownBy(() -> cut.decode(token)).isInstanceOf(JwtException.class) - .hasMessageContaining("JWT verification failed: Do not trust 'jku' token header"); - - cut.setTokenInfoExtractor(new TokenInfoExtractorImpl( - "http://malicious.ondemand.com/token_keys///myauth.ondemand.com/token_keys")); - assertThatThrownBy(() -> cut.decode(token)).isInstanceOf(JwtException.class) - .hasMessageContaining("JWT verification failed: Do not trust 'jku' token header"); - } - - @Test - public void decode_whenJwksUrlIsNotValid_throwsException() { - XsuaaJwtDecoder cut = (XsuaaJwtDecoder) new XsuaaJwtDecoderBuilder(configuration).build(); - - cut.setTokenInfoExtractor( - new TokenInfoExtractorImpl("http://myauth.ondemand.com\\@malicious.ondemand.com/token_keys")); - assertThatThrownBy(() -> cut.decode(ccToken)).isInstanceOf(JwtException.class) - .hasMessageContaining("JWT verification failed: JKU of token header is not valid"); - } - - @Test - public void decode_whenJwksContainsInvalidPath_throwsException() { - XsuaaJwtDecoder cut = (XsuaaJwtDecoder) new XsuaaJwtDecoderBuilder(configuration).build(); - cut.setTokenInfoExtractor(new TokenInfoExtractorImpl("https://subdomain.myauth.ondemand.com/wrong_endpoint")); - - assertThatThrownBy(() -> cut.decode(ccToken)).isInstanceOf(JwtException.class) - .hasMessageContaining("Jwt token does not contain a valid 'jku' header parameter"); - } - - @Test - public void decode_whenJwksContainQueryParameters_throwsException() { - XsuaaJwtDecoder cut = (XsuaaJwtDecoder) new XsuaaJwtDecoderBuilder(configuration).build(); - cut.setTokenInfoExtractor(new TokenInfoExtractorImpl("https://subdomain.myauth.ondemand.com/token_keys?a=b")); - - assertThatThrownBy(() -> cut.decode(ccToken)).isInstanceOf(JwtException.class) - .hasMessageContaining("Jwt token does not contain a valid 'jku' header parameter: "); - - } - - @Test - public void decode_whenJwksContainsFragment_throwsException() { - XsuaaJwtDecoder cut = (XsuaaJwtDecoder) new XsuaaJwtDecoderBuilder(configuration).build(); - cut.setTokenInfoExtractor( - new TokenInfoExtractorImpl("https://subdomain.myauth.ondemand.com/token_keys#token_keys")); - - assertThatThrownBy(() -> cut.decode(ccToken)).isInstanceOf(JwtException.class) - .hasMessageContaining("Jwt token does not contain a valid 'jku' header parameter:"); - } - - private static class TokenInfoExtractorImpl - implements com.sap.cloud.security.xsuaa.token.authentication.TokenInfoExtractor { - private final String jku; - - public TokenInfoExtractorImpl(String jku) { - this.jku = jku; - } - - @Override - public String getJku(JWT jwt) { - return jku; - } - - @Override - public String getKid(JWT jwt) { - return "kid"; - } - - @Override - public String getUaaDomain(JWT jwt) { - return "myauth.ondemand.com"; - } - } } \ No newline at end of file