keycloak-memoizeit
Changes
.travis.yml 1(+1 -0)
services/src/main/java/org/keycloak/authentication/authenticators/client/X509ClientAuthenticator.java 142(+142 -0)
services/src/main/resources/META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory 3(+2 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MutualTLSUtils.java 10(+7 -3)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java 22(+16 -6)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java 2(+2 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java 3(+3 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/MutualTLSClientTest.java 173(+173 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java 44(+22 -22)
travis-run-tests.sh 6(+5 -1)
Details
.travis.yml 1(+1 -0)
diff --git a/.travis.yml b/.travis.yml
index 19eccf2..f8ef9de 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,6 +17,7 @@ env:
- TESTS=old
- TESTS=crossdc-server
- TESTS=crossdc-adapter
+ - TESTS=ssl
jdk:
- oraclejdk8
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
index ce6160e..43b40e4 100755
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java
@@ -394,6 +394,14 @@ public class DefaultAuthenticationFlows {
execution.setAuthenticatorFlow(false);
realm.addAuthenticatorExecution(execution);
+ execution = new AuthenticationExecutionModel();
+ execution.setParentFlow(clients.getId());
+ execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+ execution.setAuthenticator("client-x509");
+ execution.setPriority(40);
+ execution.setAuthenticatorFlow(false);
+ realm.addAuthenticatorExecution(execution);
+
}
public static void firstBrokerLoginFlow(RealmModel realm, boolean migrate) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/X509ClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/X509ClientAuthenticator.java
new file mode 100644
index 0000000..5ad579e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/X509ClientAuthenticator.java
@@ -0,0 +1,142 @@
+package org.keycloak.authentication.authenticators.client;
+
+import org.keycloak.OAuth2Constants;
+import org.keycloak.authentication.AuthenticationFlowError;
+import org.keycloak.authentication.ClientAuthenticationFlowContext;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.ServicesLogger;
+import org.keycloak.services.x509.X509ClientCertificateLookup;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.security.GeneralSecurityException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+
+public class X509ClientAuthenticator extends AbstractClientAuthenticator {
+
+ public static final String PROVIDER_ID = "client-x509";
+ protected static ServicesLogger logger = ServicesLogger.LOGGER;
+
+ public static final AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
+ AuthenticationExecutionModel.Requirement.ALTERNATIVE,
+ AuthenticationExecutionModel.Requirement.DISABLED
+ };
+
+ @Override
+ public void authenticateClient(ClientAuthenticationFlowContext context) {
+
+ X509ClientCertificateLookup provider = context.getSession().getProvider(X509ClientCertificateLookup.class);
+ if (provider == null) {
+ logger.errorv("\"{0}\" Spi is not available, did you forget to update the configuration?",
+ X509ClientCertificateLookup.class);
+ return;
+ }
+
+ X509Certificate[] certs = new X509Certificate[0];
+ try {
+ certs = provider.getCertificateChain(context.getHttpRequest());
+ String client_id = null;
+ MediaType mediaType = context.getHttpRequest().getHttpHeaders().getMediaType();
+ boolean hasFormData = mediaType != null && mediaType.isCompatible(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
+
+ MultivaluedMap<String, String> formData = hasFormData ? context.getHttpRequest().getDecodedFormParameters() : null;
+ MultivaluedMap<String, String> queryParams = context.getHttpRequest().getUri().getQueryParameters();
+
+ if (formData != null) {
+ client_id = formData.getFirst(OAuth2Constants.CLIENT_ID);
+ }
+
+ if (client_id == null) {
+ if (queryParams != null) {
+ client_id = queryParams.getFirst(OAuth2Constants.CLIENT_ID);
+ } else {
+ Response challengeResponse = ClientAuthUtil.errorResponse(Response.Status.BAD_REQUEST.getStatusCode(), "invalid_client", "Missing client_id parameter");
+ context.challenge(challengeResponse);
+ return;
+ }
+ }
+
+ ClientModel client = context.getRealm().getClientByClientId(client_id);
+ if (client == null) {
+ context.failure(AuthenticationFlowError.CLIENT_NOT_FOUND, null);
+ return;
+ }
+ context.getEvent().client(client_id);
+ context.setClient(client);
+
+ if (!client.isEnabled()) {
+ context.failure(AuthenticationFlowError.CLIENT_DISABLED, null);
+ return;
+ }
+ } catch (GeneralSecurityException e) {
+ logger.errorf("[X509ClientCertificateAuthenticator:authenticate] Exception: %s", e.getMessage());
+ context.attempted();
+ }
+
+ if (certs == null || certs.length == 0) {
+ // No x509 client cert, fall through and
+ // continue processing the rest of the authentication flow
+ logger.debug("[X509ClientCertificateAuthenticator:authenticate] x509 client certificate is not available for mutual SSL.");
+ context.attempted();
+ return;
+ }
+
+ context.success();
+ }
+
+ public String getDisplayType() {
+ return "X509 Certificate";
+ }
+
+ @Override
+ public boolean isConfigurable() {
+ return false;
+ }
+
+ @Override
+ public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
+ return REQUIREMENT_CHOICES;
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigPropertiesPerClient() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Map<String, Object> getAdapterConfiguration(ClientModel client) {
+ Map<String, Object> result = new HashMap<>();
+ return result;
+ }
+
+ @Override
+ public Set<String> getProtocolAuthenticatorMethods(String loginProtocol) {
+ if (loginProtocol.equals(OIDCLoginProtocol.LOGIN_PROTOCOL)) {
+ Set<String> results = new HashSet<>();
+ return results;
+ } else {
+ return Collections.emptySet();
+ }
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Validates client based on a X509 Certificate";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_ID;
+ }
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory
index 329beaf..f8c259e 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.ClientAuthenticatorFactory
@@ -17,4 +17,5 @@
org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator
org.keycloak.authentication.authenticators.client.JWTClientAuthenticator
-org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator
\ No newline at end of file
+org.keycloak.authentication.authenticators.client.JWTClientSecretAuthenticator
+org.keycloak.authentication.authenticators.client.X509ClientAuthenticator
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/HOW-TO-RUN.md b/testsuite/integration-arquillian/HOW-TO-RUN.md
index 1668114..347ff20 100644
--- a/testsuite/integration-arquillian/HOW-TO-RUN.md
+++ b/testsuite/integration-arquillian/HOW-TO-RUN.md
@@ -463,6 +463,17 @@ To run the Mutual TLS Client Certificate Bound Access Tokens tests:
-Dbrowser=phantomjs \
-Dtest=org.keycloak.testsuite.hok.HoKTest
+## Run Mutual TLS for the Client tests
+
+To run the Mutual TLS test for the client:
+
+ mvn -f testsuite/integration-arquillian/pom.xml \
+ clean install \
+ -Pauth-server-wildfly \
+ -Dauth.server.ssl.required \
+ -Dbrowser=phantomjs \
+ -Dtest=org.keycloak.testsuite.client.MutualTLSClientTest
+
## Cluster tests
Cluster tests use 2 backend servers (Keycloak on Wildfly/EAP) and 1 frontend loadbalancer server node. Invalidation tests don't use loadbalancer.
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 4a6af70..a257096 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -21,6 +21,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.http.Header;
import org.apache.http.NameValuePair;
+import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
@@ -76,6 +77,8 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
+
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Form;
@@ -145,6 +148,8 @@ public class OAuthClient {
private String codeChallengeMethod;
private String origin;
+ private Supplier<CloseableHttpClient> httpClient = OAuthClient::newCloseableHttpClient;
+
public class LogoutUrlBuilder {
private final UriBuilder b = OIDCLoginProtocolService.logoutUrl(UriBuilder.fromUri(baseUrl));
@@ -243,7 +248,12 @@ public class OAuthClient {
fillLoginForm(username, password);
}
- private static CloseableHttpClient newCloseableHttpClient() {
+ public OAuthClient httpClient(Supplier<CloseableHttpClient> client) {
+ this.httpClient = client;
+ return this;
+ }
+
+ public static CloseableHttpClient newCloseableHttpClient() {
if (sslRequired) {
KeyStore keystore = null;
// load the keystore containing the client certificate - keystore type is probably jks or pkcs12
@@ -274,7 +284,7 @@ public class OAuthClient {
}
public CloseableHttpResponse doPreflightRequest() {
- try (CloseableHttpClient client = newCloseableHttpClient()) {
+ try (CloseableHttpClient client = httpClient.get()) {
HttpOptions options = new HttpOptions(getAccessTokenUrl());
options.setHeader("Origin", "http://example.com");
@@ -286,7 +296,7 @@ public class OAuthClient {
// KEYCLOAK-6771 Certificate Bound Token
public AccessTokenResponse doAccessTokenRequest(String code, String password) {
- try (CloseableHttpClient client = newCloseableHttpClient()) {
+ try (CloseableHttpClient client = httpClient.get()) {
return doAccessTokenRequest(code, password, client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -398,7 +408,7 @@ public class OAuthClient {
public AccessTokenResponse doGrantAccessTokenRequest(String realm, String username, String password, String totp,
String clientId, String clientSecret) throws Exception {
- try (CloseableHttpClient client = newCloseableHttpClient()) {
+ try (CloseableHttpClient client = httpClient.get()) {
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl(realm));
List<NameValuePair> parameters = new LinkedList<>();
@@ -444,7 +454,7 @@ public class OAuthClient {
public AccessTokenResponse doTokenExchange(String realm, String token, String targetAudience,
String clientId, String clientSecret) throws Exception {
- try (CloseableHttpClient client = newCloseableHttpClient()) {
+ try (CloseableHttpClient client = httpClient.get()) {
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl(realm));
List<NameValuePair> parameters = new LinkedList<>();
@@ -484,7 +494,7 @@ public class OAuthClient {
}
public AccessTokenResponse doTokenExchange(String realm, String clientId, String clientSecret, Map<String, String> params) throws Exception {
- try (CloseableHttpClient client = newCloseableHttpClient()) {
+ try (CloseableHttpClient client = httpClient.get()) {
HttpPost post = new HttpPost(getResourceOwnerPasswordCredentialGrantUrl(realm));
List<NameValuePair> parameters = new LinkedList<>();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
index 86a9efe..d6742cf 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/InitialFlowsTest.java
@@ -141,11 +141,13 @@ public class InitialFlowsTest extends AbstractAuthenticationTest {
addExecExport(flow, null, false, "client-secret", false, null, ALTERNATIVE, 10);
addExecExport(flow, null, false, "client-jwt", false, null, ALTERNATIVE, 20);
addExecExport(flow, null, false, "client-secret-jwt", false, null, ALTERNATIVE, 30);
+ addExecExport(flow, null, false, "client-x509", false, null, ALTERNATIVE, 40);
execs = new LinkedList<>();
addExecInfo(execs, "Client Id and Secret", "client-secret", false, 0, 0, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
addExecInfo(execs, "Signed Jwt", "client-jwt", false, 0, 1, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
addExecInfo(execs, "Signed Jwt with Client Secret", "client-secret-jwt", false, 0, 2, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
+ addExecInfo(execs, "X509 Certificate", "client-x509", false, 0, 3, ALTERNATIVE, null, new String[]{ALTERNATIVE, DISABLED});
expected.add(new FlowExecutions(flow, execs));
flow = newFlow("direct grant", "OpenID Connect Resource Owner Grant", "basic-flow", true, true);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
index e5d9471..0aa53e0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/authentication/ProvidersTest.java
@@ -81,9 +81,12 @@ public class ProvidersTest extends AbstractAuthenticationTest {
"'client_secret' sent either in request parameters or in 'Authorization: Basic' header");
addProviderInfo(expected, "testsuite-client-passthrough", "Testsuite Dummy Client Validation", "Testsuite dummy authenticator, " +
"which automatically authenticates hardcoded client (like 'test-app' )");
+ addProviderInfo(expected, "client-x509", "X509 Certificate",
+ "Validates client based on a X509 Certificate");
addProviderInfo(expected, "client-secret-jwt", "Signed Jwt with Client Secret",
"Validates client based on signed JWT issued by client and signed with the Client Secret");
+
compareProviders(expected, result);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/MutualTLSClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/MutualTLSClientTest.java
new file mode 100644
index 0000000..613e0e5
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/MutualTLSClientTest.java
@@ -0,0 +1,173 @@
+package org.keycloak.testsuite.client;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.authentication.authenticators.client.X509ClientAuthenticator;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
+import org.keycloak.testsuite.util.KeycloakModelUtils;
+import org.keycloak.testsuite.util.MutualTLSUtils;
+import org.keycloak.testsuite.util.OAuthClient;
+
+import com.google.common.base.Charsets;
+
+/**
+ * Mutual TLS Client tests.
+ */
+public class MutualTLSClientTest extends AbstractTestRealmKeycloakTest {
+
+ private static final boolean sslRequired = Boolean.parseBoolean(System.getProperty("auth.server.ssl.required"));
+
+ private static final String CLIENT_ID = "confidential-x509";
+ private static final String DISABLED_CLIENT_ID = "confidential-disabled-x509";
+ private static final String USER = "keycloak-user@localhost";
+ private static final String PASSWORD = "password";
+ private static final String REALM = "test";
+
+ @Override
+ public void configureTestRealm(RealmRepresentation testRealm) {
+ ClientRepresentation properConfiguration = KeycloakModelUtils.createClient(testRealm, CLIENT_ID);
+ properConfiguration.setServiceAccountsEnabled(Boolean.TRUE);
+ properConfiguration.setRedirectUris(Arrays.asList("https://localhost:8543/auth/realms/master/app/auth"));
+ properConfiguration.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
+
+ ClientRepresentation disabledConfiguration = KeycloakModelUtils.createClient(testRealm, DISABLED_CLIENT_ID);
+ disabledConfiguration.setServiceAccountsEnabled(Boolean.TRUE);
+ disabledConfiguration.setRedirectUris(Arrays.asList("https://localhost:8543/auth/realms/master/app/auth"));
+ disabledConfiguration.setClientAuthenticatorType(X509ClientAuthenticator.PROVIDER_ID);
+ }
+
+ @BeforeClass
+ public static void sslRequired() {
+ Assume.assumeTrue("\"auth.server.ssl.required\" is required for Mutual TLS tests", sslRequired);
+ }
+
+ @Test
+ public void testSuccessfulClientInvocationWithProperCertificate() throws Exception {
+ //given
+ Supplier<CloseableHttpClient> clientWithProperCertificate = MutualTLSUtils::newCloseableHttpClientWithDefaultKeyStoreAndTrustStore;
+
+ //when
+ OAuthClient.AccessTokenResponse token = loginAndGetAccessTokenResponse(CLIENT_ID, clientWithProperCertificate);
+
+ //then
+ assertTokenObtained(token);
+ }
+
+ @Test
+ public void testSuccessfulClientInvocationWithClientIdInQueryParams() throws Exception {
+ //given//when
+ OAuthClient.AccessTokenResponse token = null;
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ login(CLIENT_ID);
+ token = getAccessTokenResponseWithQueryParams(CLIENT_ID, client);
+ }
+
+ //then
+ assertTokenObtained(token);
+ }
+
+ @Test
+ public void testFailedClientInvocationWithoutCertificateCertificate() throws Exception {
+ //given
+ Supplier<CloseableHttpClient> clientWithoutCertificate = MutualTLSUtils::newCloseableHttpClientWithoutKeyStoreAndTrustStore;
+
+ //when
+ OAuthClient.AccessTokenResponse token = loginAndGetAccessTokenResponse(CLIENT_ID, clientWithoutCertificate);
+
+ //then
+ assertTokenNotObtained(token);
+ }
+
+ @Test
+ public void testFailedClientInvocationWithDisabledClient() throws Exception {
+ //given//when
+ OAuthClient.AccessTokenResponse token = null;
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ login(DISABLED_CLIENT_ID);
+
+ disableClient(DISABLED_CLIENT_ID);
+
+ token = getAccessTokenResponse(DISABLED_CLIENT_ID, client);
+ }
+
+ //then
+ assertTokenNotObtained(token);
+ }
+
+ private OAuthClient.AccessTokenResponse loginAndGetAccessTokenResponse(String clientId, Supplier<CloseableHttpClient> client) throws IOException{
+ try (CloseableHttpClient closeableHttpClient = client.get()) {
+ login(clientId);
+ return getAccessTokenResponse(clientId, closeableHttpClient);
+ } catch (IOException ioe) {
+ throw ioe;
+ }
+ }
+
+ private OAuthClient.AccessTokenResponse getAccessTokenResponse(String clientId, CloseableHttpClient closeableHttpClient) {
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ // Call protected endpoint with supplied client.
+ return oauth
+ .httpClient(() -> closeableHttpClient)
+ .clientId(clientId)
+ .doAccessTokenRequest(code, null, closeableHttpClient);
+ }
+
+ private void login(String clientId) {
+ // Login with default client, despite what has been supplied into this method.
+ oauth
+ .httpClient(OAuthClient::newCloseableHttpClient)
+ .clientId(clientId)
+ .doLogin(USER, PASSWORD);
+ }
+
+ private void assertTokenObtained(OAuthClient.AccessTokenResponse token) {
+ Assert.assertEquals(200, token.getStatusCode());
+ Assert.assertNotNull(token.getAccessToken());
+ }
+
+ private void assertTokenNotObtained(OAuthClient.AccessTokenResponse token) {
+ Assert.assertEquals(400, token.getStatusCode());
+ Assert.assertNull(token.getAccessToken());
+ }
+
+ /*
+ * This is a very simplified version of OAuthClient#doAccessTokenRequest.
+ * It test a scenario, where we do not follow the spec and specify client_id in Query Params (for in a form).
+ */
+ private OAuthClient.AccessTokenResponse getAccessTokenResponseWithQueryParams(String clientId, CloseableHttpClient client) throws Exception {
+ OAuthClient.AccessTokenResponse token;// This is a very simplified version of
+ HttpPost post = new HttpPost(oauth.getAccessTokenUrl() + "?client_id=" + clientId);
+ List<NameValuePair> parameters = new LinkedList<>();
+ parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE));
+ parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, oauth.getCurrentQuery().get(OAuth2Constants.CODE)));
+ parameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri()));
+ UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, Charsets.UTF_8);
+ post.setEntity(formEntity);
+
+ return new OAuthClient.AccessTokenResponse(client.execute(post));
+ }
+
+ private void disableClient(String clientId) {
+ ClientRepresentation disabledClientRepresentation = adminClient.realm(REALM).clients().findByClientId(clientId).get(0);
+ ClientResource disabledClientResource = adminClient.realms().realm(REALM).clients().get(disabledClientRepresentation.getId());
+ disabledClientRepresentation.setEnabled(false);
+ disabledClientResource.update(disabledClientRepresentation);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java
index 2742778..41e3d7d 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/hok/HoKTest.java
@@ -61,7 +61,7 @@ import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.drone.Different;
import org.keycloak.testsuite.util.ClientManager;
-import org.keycloak.testsuite.util.HoKTokenUtils;
+import org.keycloak.testsuite.util.MutualTLSUtils;
import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.UserInfoClientUtil;
@@ -174,7 +174,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse response;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
response = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -191,7 +191,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse response;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
response = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -238,7 +238,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
assertEquals(sessionId, token.getSessionState());
- //assertEquals(1, token.getRealmAccess().getRoles().size());
+ assertEquals(2, token.getRealmAccess().getRoles().size());
assertTrue(token.getRealmAccess().isUserInRole("user"));
assertEquals(1, token.getResourceAccess(oauth.getClientId()).getRoles().size());
@@ -258,7 +258,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
oauth.doLogin("test-user@localhost", "password");
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -272,7 +272,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
oauth2.doLogin("john-doh@localhost", "password");
String code2 = oauth2.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse2 = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
tokenResponse2 = oauth2.doAccessTokenRequest(code2, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -281,7 +281,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
// token refresh by second client by first client's refresh token
AccessTokenResponse response = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithOtherKeyStoreAndTrustStore()) {
response = oauth2.doRefreshTokenRequest(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -303,7 +303,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -325,7 +325,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
setTimeOffset(2);
AccessTokenResponse response = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
response = oauth.doRefreshTokenRequest(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -362,7 +362,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
setTimeOffset(2);
AccessTokenResponse response = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
response = oauth.doRefreshTokenRequest(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -405,7 +405,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
assertEquals(findUserByUsername(adminClient.realm("test"), username).getId(), refreshedToken.getSubject());
Assert.assertNotEquals(username, refreshedToken.getSubject());
- //assertEquals(1, refreshedToken.getRealmAccess().getRoles().size());
+ assertEquals(2, refreshedToken.getRealmAccess().getRoles().size());
Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole("user"));
assertEquals(1, refreshedToken.getResourceAccess(oauth.getClientId()).getRoles().size());
@@ -431,7 +431,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -442,8 +442,8 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
// execute the access token to get UserInfo with token binded client certificate in mutual authentication TLS
ClientBuilder clientBuilder = ClientBuilder.newBuilder();
KeyStore keystore = null;
- keystore = KeystoreUtil.loadKeyStore(HoKTokenUtils.DEFAULT_KEYSTOREPATH, HoKTokenUtils.DEFAULT_KEYSTOREPASSWORD);
- clientBuilder.keyStore(keystore, HoKTokenUtils.DEFAULT_KEYSTOREPASSWORD);
+ keystore = KeystoreUtil.loadKeyStore(MutualTLSUtils.DEFAULT_KEYSTOREPATH, MutualTLSUtils.DEFAULT_KEYSTOREPASSWORD);
+ clientBuilder.keyStore(keystore, MutualTLSUtils.DEFAULT_KEYSTOREPASSWORD);
Client client = clientBuilder.build();
WebTarget userInfoTarget = null;
Response response = null;
@@ -469,7 +469,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
AccessTokenResponse tokenResponse = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
tokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -510,7 +510,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String refreshTokenString = execPreProcessPostLogout();
CloseableHttpResponse response = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
response = oauth.doLogout(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -526,7 +526,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String refreshTokenString = execPreProcessPostLogout();
CloseableHttpResponse response = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
response = oauth.doLogout(refreshTokenString, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -596,7 +596,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
EventRepresentation loginEvent = events.expectLogin().assertEvent();
AccessTokenResponse accessTokenResponse = null;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithDefaultKeyStoreAndTrustStore()) {
accessTokenResponse = oauth.doAccessTokenRequest(code, "password", client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -605,7 +605,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
// Do token introspection
// mimic Resource Server
String tokenResponse;
- try (CloseableHttpClient client = HoKTokenUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
+ try (CloseableHttpClient client = MutualTLSUtils.newCloseableHttpClientWithoutKeyStoreAndTrustStore()) {
tokenResponse = oauth.introspectTokenWithClientCredential("confidential-cli", "secret1", "access_token", accessTokenResponse.getAccessToken(), client);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
@@ -618,7 +618,7 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
String certThumprintFromAccessToken = at.getCertConf().getCertThumbprint();
String certThumprintFromRefreshToken = rt.getCertConf().getCertThumbprint();
String certThumprintFromTokenIntrospection = rep.getCertConf().getCertThumbprint();
- String certThumprintFromBoundClientCertificate = HoKTokenUtils.getThumbprintFromDefaultClientCert();
+ String certThumprintFromBoundClientCertificate = MutualTLSUtils.getThumbprintFromDefaultClientCert();
assertTrue(rep.isActive());
assertEquals("test-user@localhost", rep.getUserName());
@@ -633,11 +633,11 @@ public class HoKTest extends AbstractTestRealmKeycloakTest {
private void verifyHoKTokenDefaultCertThumbPrint(AccessTokenResponse response) throws Exception {
- verifyHoKTokenCertThumbPrint(response, HoKTokenUtils.getThumbprintFromDefaultClientCert());
+ verifyHoKTokenCertThumbPrint(response, MutualTLSUtils.getThumbprintFromDefaultClientCert());
}
private void verifyHoKTokenOtherCertThumbPrint(AccessTokenResponse response) throws Exception {
- verifyHoKTokenCertThumbPrint(response, HoKTokenUtils.getThumbprintFromOtherClientCert());
+ verifyHoKTokenCertThumbPrint(response, MutualTLSUtils.getThumbprintFromOtherClientCert());
}
private void verifyHoKTokenCertThumbPrint(AccessTokenResponse response, String certThumbPrint) {
travis-run-tests.sh 6(+5 -1)
diff --git a/travis-run-tests.sh b/travis-run-tests.sh
index 6630ecf..ac3e2b5 100755
--- a/travis-run-tests.sh
+++ b/travis-run-tests.sh
@@ -5,7 +5,7 @@ function run-server-tests() {
mvn install -B -nsu -Pauth-server-wildfly -DskipTests
cd tests/base
- mvn test -B -nsu -Pauth-server-wildfly -Dtest=$1 2>&1 | java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer
+ mvn test -B -nsu -Pauth-server-wildfly -Dtest=$1 $2 2>&1 | java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer
exit ${PIPESTATUS[0]}
}
@@ -98,4 +98,8 @@ if [ $1 == "crossdc-adapter" ]; then
mvn clean test -B -nsu -Pcache-server-infinispan,auth-servers-crossdc-jboss,auth-server-wildfly,app-server-wildfly -Dtest=org.keycloak.testsuite.adapter.**.crossdc.**.* 2>&1 |
java -cp ../../../utils/target/classes org.keycloak.testsuite.LogTrimmer
exit ${PIPESTATUS[0]}
+fi
+
+if [ $1 == "ssl" ]; then
+ run-server-tests org.keycloak.testsuite.client.MutualTLSClientTest,org.keycloak.testsuite.hok.HoKTest "-Dauth.server.ssl.required -Dbrowser=phantomjs"
fi
\ No newline at end of file