diff --git a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/OAuthClientTest.java b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/OAuthClientTest.java index 6fe1002f2ed..bddd4452cf9 100644 --- a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/OAuthClientTest.java +++ b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/OAuthClientTest.java @@ -2,6 +2,7 @@ package org.keycloak.test.examples; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.keycloak.representations.AccessToken; import org.keycloak.testframework.annotations.InjectRealm; import org.keycloak.testframework.annotations.InjectUser; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; @@ -85,6 +86,22 @@ public class OAuthClientTest { Assertions.assertFalse(refreshResponse.isSuccess()); } + @Test + public void testParseToken() { + AccessTokenResponse accessTokenResponse = oauth.doPasswordGrantRequest(user.getUsername(), user.getPassword()); + + AccessToken accessToken = oauth.parseToken(accessTokenResponse.getAccessToken(), AccessToken.class); + Assertions.assertEquals(user.getUsername(), accessToken.getPreferredUsername()); + } + + @Test + public void testVerifyToken() { + AccessTokenResponse accessTokenResponse = oauth.doPasswordGrantRequest(user.getUsername(), user.getPassword()); + + AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken(), AccessToken.class); + Assertions.assertEquals(user.getUsername(), accessToken.getPreferredUsername()); + } + public static class OAuthUserConfig implements UserConfig { @Override diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/AbstractOAuthClient.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/AbstractOAuthClient.java index cd08d0e7e25..62124061725 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/AbstractOAuthClient.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/AbstractOAuthClient.java @@ -1,6 +1,11 @@ package org.keycloak.testsuite.util.oauth; import org.apache.http.impl.client.CloseableHttpClient; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.AuthorizationResponseToken; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.JsonWebToken; +import org.keycloak.representations.RefreshToken; import org.openqa.selenium.WebDriver; import java.util.Map; @@ -28,6 +33,8 @@ public abstract class AbstractOAuthClient { protected StateParamProvider state; protected String nonce; + private final KeyManager keyManager = new KeyManager(this); + private final TokensManager tokensManager = new TokensManager(keyManager); protected HttpClientManager httpClientManager; protected WebDriver driver; @@ -123,6 +130,30 @@ public abstract class AbstractOAuthClient { return tokenRevocationRequest(token).send(); } + public J parseToken(String token, Class clazz) { + return tokensManager.parseToken(token, clazz); + } + + public RefreshToken parseRefreshToken(String refreshToken) { + return tokensManager.parseToken(refreshToken, RefreshToken.class); + } + + public AccessToken verifyToken(String token) { + return tokensManager.verifyToken(token, AccessToken.class); + } + + public IDToken verifyIDToken(String token) { + return tokensManager.verifyToken(token, IDToken.class); + } + + public AuthorizationResponseToken verifyAuthorizationResponseToken(String token) { + return tokensManager.verifyToken(token, AuthorizationResponseToken.class); + } + + public J verifyToken(String token, Class clazz) { + return tokensManager.verifyToken(token, clazz); + } + public T baseUrl(String baseUrl) { this.baseUrl = baseUrl; return (T) this; @@ -141,10 +172,18 @@ public abstract class AbstractOAuthClient { return httpClientManager; } + public KeyManager keys() { + return keyManager; + } + public Endpoints getEndpoints() { return new Endpoints(baseUrl, config.getRealm()); } + public String getRealm() { + return config.getRealm(); + } + public String getRedirectUri() { return config.getRedirectUri(); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/JwksRequest.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/JwksRequest.java similarity index 87% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/JwksRequest.java rename to tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/JwksRequest.java index 7468f47f783..1a657741f6b 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/JwksRequest.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/JwksRequest.java @@ -7,9 +7,9 @@ import java.io.IOException; public class JwksRequest { - private final OAuthClient client; + private final AbstractOAuthClient client; - public JwksRequest(OAuthClient client) { + JwksRequest(AbstractOAuthClient client) { this.client = client; } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/JwksResponse.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/JwksResponse.java similarity index 86% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/JwksResponse.java rename to tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/JwksResponse.java index a691f087ebd..528031138a4 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/JwksResponse.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/JwksResponse.java @@ -9,7 +9,7 @@ public class JwksResponse extends AbstractHttpResponse { private JSONWebKeySet jwks; - public JwksResponse(CloseableHttpResponse response) throws IOException { + JwksResponse(CloseableHttpResponse response) throws IOException { super(response); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/KeyManager.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/KeyManager.java similarity index 96% rename from testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/KeyManager.java rename to tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/KeyManager.java index 333b1d7ac80..c32dda69928 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/KeyManager.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/KeyManager.java @@ -14,10 +14,10 @@ import java.util.Map; public class KeyManager { - private final OAuthClient client; + private final AbstractOAuthClient client; private final Map publicKeys = new HashMap<>(); - public KeyManager(OAuthClient client) { + KeyManager(AbstractOAuthClient client) { this.client = client; } diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/TokensManager.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/TokensManager.java new file mode 100644 index 00000000000..4eadfb0a59a --- /dev/null +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/TokensManager.java @@ -0,0 +1,52 @@ +package org.keycloak.testsuite.util.oauth; + +import org.keycloak.TokenVerifier; +import org.keycloak.common.VerificationException; +import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.AsymmetricSignatureVerifierContext; +import org.keycloak.crypto.KeyWrapper; +import org.keycloak.crypto.ServerECDSASignatureVerifierContext; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.representations.JsonWebToken; + +public class TokensManager { + + private final KeyManager keyManager; + + TokensManager(KeyManager keyManager) { + this.keyManager = keyManager; + } + + public T verifyToken(String token, Class clazz) { + try { + TokenVerifier verifier = TokenVerifier.create(token, clazz); + String kid = verifier.getHeader().getKeyId(); + String algorithm = verifier.getHeader().getAlgorithm().name(); + KeyWrapper key = keyManager.getPublicKey(algorithm, kid); + AsymmetricSignatureVerifierContext verifierContext; + switch (algorithm) { + case Algorithm.ES256: + case Algorithm.ES384: + case Algorithm.ES512: + verifierContext = new ServerECDSASignatureVerifierContext(key); + break; + default: + verifierContext = new AsymmetricSignatureVerifierContext(key); + } + verifier.verifierContext(verifierContext); + verifier.verify(); + return verifier.getToken(); + } catch (VerificationException e) { + throw new RuntimeException("Failed to decode token", e); + } + } + + public T parseToken(String token, Class clazz) { + try { + return new JWSInput(token).readJsonContent(clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SignatureSignerUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SignatureSignerUtil.java new file mode 100644 index 00000000000..8ebfdd1443f --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/SignatureSignerUtil.java @@ -0,0 +1,35 @@ +package org.keycloak.testsuite.util; + +import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.AsymmetricSignatureSignerContext; +import org.keycloak.crypto.KeyWrapper; +import org.keycloak.crypto.ServerECDSASignatureSignerContext; +import org.keycloak.crypto.SignatureSignerContext; + +import java.security.PrivateKey; + +public class SignatureSignerUtil { + + public static SignatureSignerContext createSigner(PrivateKey privateKey, String kid, String algorithm) { + return createSigner(privateKey, kid, algorithm, null); + } + + public static SignatureSignerContext createSigner(PrivateKey privateKey, String kid, String algorithm, String curve) { + KeyWrapper keyWrapper = new KeyWrapper(); + keyWrapper.setAlgorithm(algorithm); + keyWrapper.setKid(kid); + keyWrapper.setPrivateKey(privateKey); + keyWrapper.setCurve(curve); + SignatureSignerContext signer; + switch (algorithm) { + case Algorithm.ES256: + case Algorithm.ES384: + case Algorithm.ES512: + signer = new ServerECDSASignatureSignerContext(keyWrapper); + break; + default: + signer = new AsymmetricSignatureSignerContext(keyWrapper); + } + return signer; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/OAuthClient.java index b0d18325242..96cba29ab1e 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/OAuthClient.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/oauth/OAuthClient.java @@ -27,32 +27,16 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.message.BasicNameValuePair; import org.keycloak.OAuth2Constants; -import org.keycloak.TokenVerifier; import org.keycloak.broker.provider.util.SimpleHttp; -import org.keycloak.common.VerificationException; -import org.keycloak.crypto.Algorithm; -import org.keycloak.crypto.AsymmetricSignatureSignerContext; -import org.keycloak.crypto.AsymmetricSignatureVerifierContext; -import org.keycloak.crypto.KeyWrapper; -import org.keycloak.crypto.ServerECDSASignatureSignerContext; -import org.keycloak.crypto.ServerECDSASignatureVerifierContext; -import org.keycloak.crypto.SignatureSignerContext; -import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.Constants; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; -import org.keycloak.representations.AccessToken; -import org.keycloak.representations.AuthorizationResponseToken; import org.keycloak.representations.ClaimsRepresentation; -import org.keycloak.representations.IDToken; -import org.keycloak.representations.JsonWebToken; -import org.keycloak.representations.RefreshToken; import org.keycloak.testsuite.broker.util.SimpleHttpDefault; import org.keycloak.testsuite.pages.LoginPage; -import org.keycloak.testsuite.runonserver.RunOnServerException; import org.keycloak.testsuite.util.DroneUtils; import org.keycloak.testsuite.util.WaitUtils; import org.keycloak.util.BasicAuthHelper; @@ -62,13 +46,10 @@ import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.PageFactory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -84,17 +65,11 @@ import static org.keycloak.testsuite.util.UIUtils.clickLink; */ public class OAuthClient extends AbstractOAuthClient { - private static final Logger logger = LoggerFactory.getLogger(OAuthClient.class); - public static String SERVER_ROOT; public static String AUTH_SERVER_ROOT; public static String APP_ROOT; public static String APP_AUTH_ROOT; - - - - static { updateURLs(getAuthServerContextRoot()); } @@ -115,7 +90,6 @@ public class OAuthClient extends AbstractOAuthClient { updateAppRootRealm("master"); } - private KeyManager keyManager = new KeyManager(this); private String idTokenHint; @@ -530,74 +504,6 @@ public class OAuthClient extends AbstractOAuthClient { } } - // TODO Extract into separate util to verify tokens - public AccessToken verifyToken(String token) { - return verifyToken(token, AccessToken.class); - } - - public IDToken verifyIDToken(String token) { - return verifyToken(token, IDToken.class); - } - - public AuthorizationResponseToken verifyAuthorizationResponseToken(String token) { - return verifyToken(token, AuthorizationResponseToken.class); - } - - public RefreshToken parseRefreshToken(String refreshToken) { - try { - return new JWSInput(refreshToken).readJsonContent(RefreshToken.class); - } catch (Exception e) { - throw new RunOnServerException(e); - } - } - - public T verifyToken(String token, Class clazz) { - try { - TokenVerifier verifier = TokenVerifier.create(token, clazz); - String kid = verifier.getHeader().getKeyId(); - String algorithm = verifier.getHeader().getAlgorithm().name(); - KeyWrapper key = keyManager.getPublicKey(algorithm, kid); - AsymmetricSignatureVerifierContext verifierContext; - switch (algorithm) { - case Algorithm.ES256: - case Algorithm.ES384: - case Algorithm.ES512: - verifierContext = new ServerECDSASignatureVerifierContext(key); - break; - default: - verifierContext = new AsymmetricSignatureVerifierContext(key); - } - verifier.verifierContext(verifierContext); - verifier.verify(); - return verifier.getToken(); - } catch (VerificationException e) { - throw new RuntimeException("Failed to decode token", e); - } - } - - public SignatureSignerContext createSigner(PrivateKey privateKey, String kid, String algorithm) { - return createSigner(privateKey, kid, algorithm, null); - } - - public SignatureSignerContext createSigner(PrivateKey privateKey, String kid, String algorithm, String curve) { - KeyWrapper keyWrapper = new KeyWrapper(); - keyWrapper.setAlgorithm(algorithm); - keyWrapper.setKid(kid); - keyWrapper.setPrivateKey(privateKey); - keyWrapper.setCurve(curve); - SignatureSignerContext signer; - switch (algorithm) { - case Algorithm.ES256: - case Algorithm.ES384: - case Algorithm.ES512: - signer = new ServerECDSASignatureSignerContext(keyWrapper); - break; - default: - signer = new AsymmetricSignatureSignerContext(keyWrapper); - } - return signer; - } - public String getClientId() { return config.getClientId(); } @@ -739,15 +645,6 @@ public class OAuthClient extends AbstractOAuthClient { return this; } - - public KeyManager keys() { - return keyManager; - } - - public String getRealm() { - return config.getRealm(); - } - public OAuthClient codeVerifier(String codeVerifier) { this.codeVerifier = codeVerifier; return this; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/AbstractClientPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/AbstractClientPoliciesTest.java index 105957d6ec6..ff4f5a8f647 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/AbstractClientPoliciesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/policies/AbstractClientPoliciesTest.java @@ -175,6 +175,7 @@ import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientPolicyBuilder; import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfileBuilder; import org.keycloak.testsuite.util.ClientPoliciesUtil.ClientProfilesBuilder; import org.keycloak.testsuite.util.MutualTLSUtils; +import org.keycloak.testsuite.util.SignatureSignerUtil; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse; import org.keycloak.testsuite.util.ServerURLs; @@ -494,7 +495,7 @@ public abstract class AbstractClientPoliciesTest extends AbstractKeycloakTest { protected String createSignedRequestToken(String clientId, PrivateKey privateKey, PublicKey publicKey, String algorithm) { JsonWebToken jwt = createRequestToken(clientId, getRealmInfoUrl()); String kid = KeyUtils.createKeyId(publicKey); - SignatureSignerContext signer = oauth.createSigner(privateKey, kid, algorithm); + SignatureSignerContext signer = SignatureSignerUtil.createSigner(privateKey, kid, algorithm); return new JWSBuilder().kid(kid).jsonContent(jwt).sign(signer); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AbstractClientAuthSignedJWTTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AbstractClientAuthSignedJWTTest.java index 68caeaf325e..12d69cb6e5d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AbstractClientAuthSignedJWTTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AbstractClientAuthSignedJWTTest.java @@ -117,6 +117,7 @@ import org.keycloak.testsuite.rest.resource.TestingOIDCEndpointsApplicationResou import org.keycloak.testsuite.util.ClientBuilder; import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.KeystoreUtils; +import org.keycloak.testsuite.util.SignatureSignerUtil; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; import org.keycloak.testsuite.util.oauth.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; @@ -925,7 +926,7 @@ public abstract class AbstractClientAuthSignedJWTTest extends AbstractKeycloakTe if (kid == null) { kid = KeyUtils.createKeyId(publicKey); } - SignatureSignerContext signer = oauth.createSigner(privateKey, kid, algorithm, curve); + SignatureSignerContext signer = SignatureSignerUtil.createSigner(privateKey, kid, algorithm, curve); String ret = new JWSBuilder().kid(kid).jsonContent(jwt).sign(signer); return ret; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java index 3347cc0d195..e91d1c6dc92 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java @@ -45,6 +45,7 @@ import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.KeystoreUtils; +import org.keycloak.testsuite.util.SignatureSignerUtil; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; import java.security.KeyPair; @@ -619,7 +620,7 @@ public class ClientAuthSignedJWTTest extends AbstractClientAuthSignedJWTTest { PrivateKey privateKey = keyPair.getPrivate(); JsonWebToken assertion = createRequestToken(app2.getClientId(), getRealmInfoUrl()); - SignatureSignerContext signer = oauth.createSigner(privateKey, null, Algorithm.ES512); + SignatureSignerContext signer = SignatureSignerUtil.createSigner(privateKey, null, Algorithm.ES512); String jws = new JWSBuilder().jsonContent(assertion).sign(signer); List parameters = new LinkedList<>();