@@ -81,5 +85,32 @@
true
+
+
+
+ maven-dependency-plugin
+
+
+ unpack-javascript-adapter
+ generate-resources
+
+ unpack
+
+
+
+
+ org.keycloak
+ keycloak-js-adapter
+ ${js-adapter.version}
+ jar
+ ${pom.basedir}/src/main/resources/javascript
+
+
+ **/keycloak.js
+
+
+
+
+
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index 7b76083ec09..1e5b9ab50b8 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -62,6 +62,7 @@ import org.keycloak.testsuite.forms.PassThroughAuthenticator;
import org.keycloak.testsuite.forms.PassThroughClientAuthenticator;
import org.keycloak.testsuite.rest.representation.AuthenticatorState;
import org.keycloak.testsuite.rest.resource.TestCacheResource;
+import org.keycloak.testsuite.rest.resource.TestJavascriptResource;
import org.keycloak.testsuite.rest.resource.TestingExportImportResource;
import org.keycloak.testsuite.runonserver.ModuleUtil;
import org.keycloak.testsuite.runonserver.FetchOnServer;
@@ -759,6 +760,11 @@ public class TestingResourceProvider implements RealmResourceProvider {
}
}
+ @Path("/javascript")
+ public TestJavascriptResource getJavascriptResource() {
+ return new TestJavascriptResource();
+ }
+
private RealmModel getRealmByName(String realmName) {
RealmProvider realmProvider = session.getProvider(RealmProvider.class);
return realmProvider.getRealmByName(realmName);
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java
new file mode 100644
index 00000000000..cb7ed2c69e2
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestJavascriptResource.java
@@ -0,0 +1,53 @@
+package org.keycloak.testsuite.rest.resource;
+
+import org.keycloak.testsuite.rest.TestingResourceProvider;
+
+import javax.print.attribute.standard.Media;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+/**
+ * @author mhajas
+ */
+public class TestJavascriptResource {
+
+ @GET
+ @Path("/js/keycloak.js")
+ @Produces("application/javascript")
+ public String getJavascriptAdapter() throws IOException {
+ return resourceToString("/javascript/keycloak.js");
+ }
+
+ @GET
+ @Path("/index.html")
+ @Produces(MediaType.TEXT_HTML)
+ public String getJavascriptTestingEnvironment() throws IOException {
+ return resourceToString("/javascript/index.html");
+ }
+
+ @GET
+ @Path("/keycloak.json")
+ @Produces(MediaType.APPLICATION_JSON)
+ public String getKeycloakJSON() throws IOException {
+ return resourceToString("/javascript/keycloak.json");
+ }
+
+ private String resourceToString(String path) throws IOException {
+ InputStream is = TestingResourceProvider.class.getResourceAsStream(path);
+ BufferedReader buf = new BufferedReader(new InputStreamReader(is));
+ String line = buf.readLine();
+ StringBuilder sb = new StringBuilder();
+ while (line != null) {
+ sb.append(line).append("\n");
+ line = buf.readLine();
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/index.html b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/index.html
new file mode 100644
index 00000000000..b352c0846d1
--- /dev/null
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/index.html
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+Result
+
+
+Events
+
+
+
+
+
+
diff --git a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/keycloak.json b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/keycloak.json
similarity index 94%
rename from testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/keycloak.json
rename to testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/keycloak.json
index 00f6c9e57ec..989e71efe29 100644
--- a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/keycloak.json
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/javascript/keycloak.json
@@ -1,5 +1,5 @@
{
- "realm" : "example",
+ "realm" : "test",
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
"auth-server-url" : "http://localhost:8180/auth",
"ssl-required" : "external",
diff --git a/testsuite/integration-arquillian/test-apps/js-console/README.md b/testsuite/integration-arquillian/test-apps/js-console/README.md
deleted file mode 100644
index fa9a02d1920..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-console/README.md
+++ /dev/null
@@ -1,17 +0,0 @@
-Basic JavaScript Example
-========================
-
-Start and configure Keycloak
-----------------------------
-
-Start Keycloak:
-
- bin/standalone.sh
-
-Open the Keycloak admin console, click on Add Realm, click on 'Choose a JSON file', selct example-realm.json and click Upload.
-
-Deploy the JS Console to Keycloak by running:
-
- mvn install wildfly:deploy
-
-Open the console at http://localhost:8080/js-console and login with username: 'user', and password: 'password'.
diff --git a/testsuite/integration-arquillian/test-apps/js-console/example-realm.json b/testsuite/integration-arquillian/test-apps/js-console/example-realm.json
deleted file mode 100755
index 0a90d2ca034..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-console/example-realm.json
+++ /dev/null
@@ -1,88 +0,0 @@
-{
- "realm": "example",
- "enabled": true,
- "sslRequired": "external",
- "registrationAllowed": true,
- "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
- "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
- "requiredCredentials": [ "password" ],
- "users" : [
- {
- "username" : "user",
- "enabled": true,
- "email" : "sample-user@example",
- "firstName": "Sample",
- "lastName": "User",
- "credentials" : [
- { "type" : "password",
- "value" : "password" }
- ],
- "realmRoles": [ "user" ],
- "clientRoles": {
- "realm-management" : [ "view-realm", "manage-users" ],
- "account": ["view-profile", "manage-account"]
- }
- },{
- "username" : "unauthorized",
- "enabled": true,
- "email" : "sample-user2@example",
- "firstName": "Sample",
- "lastName": "User",
- "credentials" : [
- { "type" : "password",
- "value" : "password" }
- ],
- "realmRoles": [],
- "clientRoles": {
- "account": ["view-profile", "manage-account"]
- }
- }
- ],
- "roles" : {
- "realm" : [
- {
- "name": "user",
- "description": "User privileges"
- },
- {
- "name": "admin",
- "description": "Administrator privileges"
- }
- ]
- },
- "scopeMappings": [
- {
- "client": "js-console",
- "roles": ["user"]
- }
- ],
- "clients": [
- {
- "clientId": "js-console",
- "enabled": true,
- "publicClient": true,
- "baseUrl": "/js-console",
- "redirectUris": [
- "/js-console/*"
- ],
- "webOrigins": [
- "http://localhost:8280",
- "https://localhost:8643"
- ]
- },{
- "clientId": "js-database",
- "enabled": true,
- "adminUrl": "/database",
- "baseUrl": "/database",
- "bearerOnly": true
- }
- ],
- "clientScopeMappings": {
- "account": [
- {
- "client": "js-console",
- "roles": ["view-profile"]
- }
- ]
- }
-}
diff --git a/testsuite/integration-arquillian/test-apps/js-console/pom.xml b/testsuite/integration-arquillian/test-apps/js-console/pom.xml
deleted file mode 100755
index e23f99bfaf2..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-console/pom.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
- org.keycloak.testsuite
- integration-arquillian-test-apps
- 4.0.0.CR1-SNAPSHOT
-
-
- 4.0.0
- integration-arquillian-test-apps-js-console
- war
- JS Console
-
-
-
- ${project.version}
-
-
-
- js-console
-
-
- maven-dependency-plugin
-
-
- unpack-javascript-adapter
- generate-resources
-
- unpack
-
-
-
-
- org.keycloak
- keycloak-js-adapter
- ${js-adapter.version}
- jar
- ${pom.basedir}/target/js-console/js
-
-
- **/keycloak.js
-
-
-
-
-
- org.jboss.as.plugins
- jboss-as-maven-plugin
-
- false
-
-
-
- org.wildfly.plugins
- wildfly-maven-plugin
-
- false
-
-
-
-
-
diff --git a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/WEB-INF/web.xml
deleted file mode 100644
index 16e8b239d24..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
- js-console
-
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html b/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html
deleted file mode 100755
index 65d58f15759..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-console/src/main/webapp/index.html
+++ /dev/null
@@ -1,363 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Result
-
-
-Events
-
-
-Info
-TimeSkew:
-
-
-
-
-
-
diff --git a/testsuite/integration-arquillian/test-apps/js-database/pom.xml b/testsuite/integration-arquillian/test-apps/js-database/pom.xml
deleted file mode 100644
index 078d7be7f5e..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-database/pom.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
- integration-arquillian-test-apps
- org.keycloak.testsuite
- 4.0.0.CR1-SNAPSHOT
-
-
- 4.0.0
- integration-arquillian-test-apps-js-database
- JAX-RS Database Service Using OAuth Bearer Tokens
- war
-
-
-
- jboss
- jboss repo
- https://repository.jboss.org/nexus/content/groups/public/
-
- false
-
-
-
-
-
-
- org.jboss.resteasy
- resteasy-jaxrs
- provided
-
-
- org.jboss.spec.javax.servlet
- jboss-servlet-api_3.0_spec
- provided
-
-
- org.keycloak
- keycloak-core
- provided
-
-
- org.keycloak
- keycloak-adapter-core
- provided
-
-
- org.apache.httpcomponents
- httpclient
- provided
-
-
-
-
- js-database
-
-
- org.jboss.as.plugins
- jboss-as-maven-plugin
-
- false
-
-
-
- org.wildfly.plugins
- wildfly-maven-plugin
-
- false
-
-
-
-
-
diff --git a/testsuite/integration-arquillian/test-apps/js-database/src/main/java/org/keycloak/example/oauth/CustomerService.java b/testsuite/integration-arquillian/test-apps/js-database/src/main/java/org/keycloak/example/oauth/CustomerService.java
deleted file mode 100755
index d2b193eb60c..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-database/src/main/java/org/keycloak/example/oauth/CustomerService.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2016 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.example.oauth;
-
-import org.jboss.resteasy.annotations.cache.NoCache;
-import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.KeycloakSecurityContext;
-import org.keycloak.representations.AccessToken;
-
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
-import javax.ws.rs.core.Context;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @author Bill Burke
- * @version $Revision: 1 $
- */
-@Path("customers")
-public class CustomerService {
-
- @Context
- private HttpRequest httpRequest;
-
- @GET
- @Produces("application/json")
- @NoCache
- public List getCustomers() {
- // Just to show how to user info from access token in REST endpoint
- KeycloakSecurityContext securityContext = (KeycloakSecurityContext) httpRequest.getAttribute(KeycloakSecurityContext.class.getName());
- AccessToken accessToken = securityContext.getToken();
- System.out.println(String.format("User '%s' with email '%s' made request to CustomerService REST endpoint", accessToken.getPreferredUsername(), accessToken.getEmail()));
-
- ArrayList rtn = new ArrayList();
- rtn.add("Bill Burke");
- rtn.add("Stian Thorgersen");
- rtn.add("Stan Silvert");
- rtn.add("Gabriel Cardoso");
- rtn.add("Viliam Rockai");
- rtn.add("Marek Posolda");
- rtn.add("Boleslaw Dawidowicz");
- return rtn;
- }
-}
diff --git a/testsuite/integration-arquillian/test-apps/js-database/src/main/webapp/WEB-INF/keycloak.json b/testsuite/integration-arquillian/test-apps/js-database/src/main/webapp/WEB-INF/keycloak.json
deleted file mode 100755
index 9f94148a6de..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-database/src/main/webapp/WEB-INF/keycloak.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "realm" : "example",
- "resource" : "js-database",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
- "auth-server-url": "http://localhost:8180/auth",
- "bearer-only" : true,
- "ssl-required" : "external"
-}
diff --git a/testsuite/integration-arquillian/test-apps/js-database/src/main/webapp/WEB-INF/web.xml b/testsuite/integration-arquillian/test-apps/js-database/src/main/webapp/WEB-INF/web.xml
deleted file mode 100755
index 599f6679ed7..00000000000
--- a/testsuite/integration-arquillian/test-apps/js-database/src/main/webapp/WEB-INF/web.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
- js-database
-
-
-
- /*
-
-
-
- user
-
-
-
-
- KEYCLOAK
- example
-
-
-
- user
-
-
diff --git a/testsuite/integration-arquillian/test-apps/pom.xml b/testsuite/integration-arquillian/test-apps/pom.xml
index 5a33513947a..e6c76e8bc61 100644
--- a/testsuite/integration-arquillian/test-apps/pom.xml
+++ b/testsuite/integration-arquillian/test-apps/pom.xml
@@ -15,9 +15,7 @@
Test apps
- js-console
test-apps-dist
- js-database
photoz
hello-world-authz-service
servlet-authz
diff --git a/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml b/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
index e3200c5a462..a9cd8061124 100755
--- a/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
+++ b/testsuite/integration-arquillian/test-apps/test-apps-dist/build.xml
@@ -19,14 +19,6 @@
-
-
-
-
-
-
-
-
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java
deleted file mode 100755
index 40063da9366..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSConsoleTestApp.java
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright 2016 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.testsuite.adapter.page;
-
-import org.jboss.arquillian.container.test.api.OperateOnDeployment;
-import org.jboss.arquillian.test.api.ArquillianResource;
-import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
-import org.openqa.selenium.WebElement;
-import org.openqa.selenium.support.FindBy;
-import org.openqa.selenium.support.ui.Select;
-
-import java.net.URL;
-
-/**
- *
- * @author tkyjovsk
- */
-public class JSConsoleTestApp extends AbstractPageWithInjectedUrl {
-
- public static final String DEPLOYMENT_NAME = "js-console-example";
- public static final String CLIENT_ID = "integration-arquillian-test-apps-js-console";
-
- @ArquillianResource
- @OperateOnDeployment(DEPLOYMENT_NAME)
- private URL url;
-
- @Override
- public URL getInjectedUrl() {
- //EAP6 URL fix
- URL fixedUrl = createInjectedURL("js-console");
- return fixedUrl != null ? fixedUrl : url;
- }
-
- @FindBy(xpath = "//button[text() = 'Init']")
- private WebElement initButton;
- @FindBy(xpath = "//button[text() = 'Init with both tokens']")
- private WebElement initWithBothTokens;
- @FindBy(xpath = "//button[text() = 'Init with refresh token']")
- private WebElement initWithRefreshToken;
- @FindBy(xpath = "//button[text() = 'Init with TimeSkew']")
- private WebElement initWithTimeSkew;
- @FindBy(xpath = "//button[text() = 'Init with different realm name']")
- private WebElement initWithDifferentRealmName;
- @FindBy(xpath = "//button[text() = 'Login']")
- private WebElement logInButton;
- @FindBy(xpath = "//button[text() = 'Logout']")
- private WebElement logOutButton;
- @FindBy(xpath = "//button[text() = 'Refresh Token']")
- private WebElement refreshTokenButton;
- @FindBy(xpath = "//button[contains(text(),'Refresh Token (if <30s')]")
- private WebElement refreshTokenIfUnder30sButton;
- @FindBy(xpath = "//button[contains(text(),'Refresh Token (if <5s')]")
- private WebElement refreshTokenIfUnder5sButton;
- @FindBy(xpath = "//button[text() = 'Get Profile']")
- private WebElement getProfileButton;
-
- @FindBy(xpath = "//button[text() = 'Show Error Response']")
- private WebElement showErrorButton;
- @FindBy(xpath = "//button[text() = 'Show Token']")
- private WebElement showTokenButton;
- @FindBy(xpath = "//button[text() = 'Show Refresh Token']")
- private WebElement showRefreshTokenButton;
- @FindBy(xpath = "//button[text() = 'Show ID Token']")
- private WebElement showIdTokenButton;
- @FindBy(xpath = "//button[text() = 'Show Expires']")
- private WebElement showExpiresButton;
- @FindBy(xpath = "//button[text() = 'Show Details']")
- private WebElement showDetailsButton;
- @FindBy(xpath = "//button[text() = 'Create Bearer Request']")
- private WebElement createBearerRequest;
- @FindBy(xpath = "//button[text() = 'Bearer to keycloak']")
- private WebElement createBearerRequestToKeycloakButton;
- @FindBy(xpath = "//button[text() = 'Cert request']")
- private WebElement certRequestButton;
- @FindBy(xpath = "//button[text() = 'refresh timeSkew']")
- private WebElement refreshTimeSkewButton;
- @FindBy(xpath = "//button[text() = 'Create user']")
- private WebElement createUserButton;
- @FindBy(xpath = "//button[text() = 'Reentrancy callback']")
- private WebElement reentrancyCallbackButton;
-
- @FindBy(id = "timeSkew")
- private WebElement timeSkewValue;
- @FindBy(id = "inputField")
- private WebElement generalInput;
- @FindBy(id = "inputField2")
- private WebElement generalInput2;
- @FindBy(id = "inputField3")
- private WebElement generalInput3;
- @FindBy(xpath = "//button[text() = 'timeSkew offset']")
- private WebElement timeSkewButton;
-
- @FindBy(id = "flowSelect")
- private Select flowSelect;
- @FindBy(id = "responseModeSelect")
- private Select responseModeSelect;
- @FindBy(id = "onLoad")
- private Select onLoad;
-
-
- @FindBy(id = "output")
- private WebElement outputArea;
-
- @FindBy(id = "events")
- private WebElement eventsArea;
-
- public void logIn() {
- logInButton.click();
- }
-
- public void logOut() {
- logOutButton.click();
- }
-
- public void refreshToken() {
- refreshTokenButton.click();
- }
-
- public void refreshTokenIfUnder30s() {
- refreshTokenIfUnder30sButton.click();
- }
-
- public void refreshTokenIfUnder5s() {
- refreshTokenIfUnder5sButton.click();
- }
-
- public void getProfile() {
- getProfileButton.click();
- }
-
- public void setFlow(String value) {
- flowSelect.selectByValue(value);
- }
-
- public void setOnLoad(String value) {
- onLoad.selectByValue(value);
- }
-
- public void init() {
- initButton.click();
- }
-
- public void initWithBothTokens() {
- initWithBothTokens.click();
- }
-
- public void initWithRefreshToken() {
- initWithRefreshToken.click();
- }
-
- public void initWithTimeSkew() {
- initWithTimeSkew.click();
- }
-
- public void initWithDifferentRealmName() {
- initWithDifferentRealmName.click();
- }
-
- public void createBearerRequest() {
- createBearerRequest.click();
- }
-
- public void createBearerRequestToKeycloak() {
- createBearerRequestToKeycloakButton.click();
- }
-
- public void setResponseMode(String value) {
- responseModeSelect.selectByValue(value);
- }
-
- public WebElement getOutputElement() {
- return outputArea;
- }
-
- public WebElement getEventsElement() {
- return eventsArea;
- }
-
- public WebElement getInitButtonElement() {
- return initButton;
- }
-
- public void showErrorResponse() {
- showErrorButton.click();
- }
-
- public WebElement getTimeSkewValue() {
- return timeSkewValue;
- }
-
- public void setInput(String value) {
- generalInput.clear();
- generalInput.sendKeys(value);
- }
-
- public void setInput2(String value) {
- generalInput2.clear();
- generalInput2.sendKeys(value);
- }
-
- public void setInput3(String value) {
- generalInput3.clear();
- generalInput3.sendKeys(value);
- }
-
- public void setInput(int value) {
- setInput(Integer.toString(value));
- }
-
- public void setTimeSkew(int value) {
- setInput(value);
- timeSkewButton.click();
- }
-
- public void refreshTimeSkew() {
- refreshTimeSkewButton.click();
- }
-
- public void createUserRequest() {
- createUserButton.click();
- }
-
- public void sendCertRequest() {
- certRequestButton.click();
- }
-
- public void callReentrancyCallback() {
- reentrancyCallbackButton.click();
- }
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSDatabaseTestApp.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSDatabaseTestApp.java
deleted file mode 100644
index 83afdb28933..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/JSDatabaseTestApp.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2016 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.testsuite.adapter.page;
-
-import org.jboss.arquillian.container.test.api.OperateOnDeployment;
-import org.jboss.arquillian.test.api.ArquillianResource;
-import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl;
-
-import java.net.URL;
-
-/**
- *
- * @author tkyjovsk
- */
-public class JSDatabaseTestApp extends AbstractPageWithInjectedUrl {
-
- public static final String DEPLOYMENT_NAME = "js-database-example";
- public static final String CLIENT_ID = "integration-arquillian-test-apps-js-database";
-
- @ArquillianResource
- @OperateOnDeployment(DEPLOYMENT_NAME)
- private URL url;
-
- @Override
- public URL getInjectedUrl() {
- //EAP6 URL fix
- URL fixedUrl = createInjectedURL("js-database");
- return fixedUrl != null ? fixedUrl : url;
- }
-
-}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java
index 55b9d3aa6dc..f3312d9359c 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/auth/page/login/OAuthGrant.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.auth.page.login;
+import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -38,8 +39,14 @@ public class OAuthGrant extends LoginActions {
cancelButton.click();
}
+
+ public boolean isCurrent(WebDriver driver1) {
+ if (driver1 == null) driver1 = driver;
+ return driver1.getPageSource().contains("Do you grant these access privileges");
+ }
+
@Override
public boolean isCurrent() {
- return driver.getPageSource().contains("Do you grant these access privileges");
+ return isCurrent(null);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
index dc6bfaffd83..04daf8da81a 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/client/resources/TestingResource.java
@@ -286,4 +286,14 @@ public interface TestingResource {
@Produces(MediaType.TEXT_PLAIN_UTF_8)
String runOnServer(String runOnServer);
+ @GET
+ @Path("js/keycloak.js")
+ @Produces(MediaType.TEXT_HTML_UTF_8)
+ String getJavascriptAdapter();
+
+ @GET
+ @Path("/get-javascript-testing-environment")
+ @Produces(MediaType.TEXT_HTML_UTF_8)
+ String getJavascriptTestingEnvironment();
+
}
diff --git a/testsuite/integration-arquillian/test-apps/js-database/src/main/java/org/keycloak/example/oauth/DataApplication.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/JavascriptBrowser.java
old mode 100755
new mode 100644
similarity index 59%
rename from testsuite/integration-arquillian/test-apps/js-database/src/main/java/org/keycloak/example/oauth/DataApplication.java
rename to testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/JavascriptBrowser.java
index 6456904af63..687d95126e6
--- a/testsuite/integration-arquillian/test-apps/js-database/src/main/java/org/keycloak/example/oauth/DataApplication.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/JavascriptBrowser.java
@@ -14,17 +14,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package org.keycloak.testsuite.util;
-package org.keycloak.example.oauth;
+import org.jboss.arquillian.drone.api.annotation.Qualifier;
-import javax.ws.rs.ApplicationPath;
-import javax.ws.rs.core.Application;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
/**
- * @author Bill Burke
- * @version $Revision: 1 $
+ *
+ * @author Petr Mensik
*/
-@ApplicationPath("/")
-public class DataApplication extends Application
-{
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+@Qualifier
+public @interface JavascriptBrowser {
}
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 6ea17c683a4..497947d1f52 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
@@ -50,6 +50,7 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;
@@ -69,6 +70,8 @@ import java.security.KeyStore;
import java.security.PublicKey;
import java.util.*;
+import static org.keycloak.testsuite.admin.Users.getPasswordOf;
+
/**
* @author Stian Thorgersen
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
@@ -197,6 +200,10 @@ public class OAuthClient {
origin = null;
}
+ public void setDriver(WebDriver driver) {
+ this.driver = driver;
+ }
+
public AuthorizationEndpointResponse doLogin(String username, String password) {
openLoginForm();
fillLoginForm(username, password);
@@ -204,6 +211,11 @@ public class OAuthClient {
return new AuthorizationEndpointResponse(this);
}
+ public AuthorizationEndpointResponse doLogin(UserRepresentation user) {
+
+ return doLogin(user.getUsername(), getPasswordOf(user));
+ }
+
public void fillLoginForm(String username, String password) {
WaitUtils.waitForPageToLoad();
String src = driver.getPageSource();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
deleted file mode 100755
index fcc7ff35539..00000000000
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * Copyright 2016 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.keycloak.testsuite.adapter.example;
-
-import org.jboss.arquillian.container.test.api.Deployment;
-import org.jboss.arquillian.graphene.page.Page;
-import org.jboss.shrinkwrap.api.spec.WebArchive;
-import org.junit.Test;
-import org.keycloak.OAuth2Constants;
-import org.keycloak.admin.client.resource.ClientResource;
-import org.keycloak.admin.client.resource.UsersResource;
-import org.keycloak.representations.idm.ClientRepresentation;
-import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
-import org.keycloak.testsuite.adapter.page.JSConsoleTestApp;
-import org.keycloak.testsuite.adapter.page.JSDatabaseTestApp;
-import org.keycloak.testsuite.admin.ApiUtil;
-import org.keycloak.testsuite.auth.page.account.Applications;
-import org.keycloak.testsuite.auth.page.login.OAuthGrant;
-import org.keycloak.testsuite.console.page.events.Config;
-import org.keycloak.testsuite.console.page.events.LoginEvents;
-import org.keycloak.testsuite.util.OAuthClient;
-import org.keycloak.testsuite.util.RealmBuilder;
-import org.openqa.selenium.By;
-import org.openqa.selenium.TimeoutException;
-import org.openqa.selenium.WebElement;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-
-import static org.hamcrest.Matchers.containsString;
-import static org.junit.Assert.*;
-import static org.keycloak.testsuite.auth.page.AuthRealm.EXAMPLE;
-import static org.keycloak.testsuite.util.IOUtil.loadRealm;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
-import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
-import static org.keycloak.testsuite.util.WaitUtils.pause;
-import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
-
-public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampleAdapterTest {
-
- @Page
- private JSConsoleTestApp jsConsoleTestAppPage;
-
- @Page
- private Config configPage;
-
- @Page
- private LoginEvents loginEventsPage;
-
- @Page
- private OAuthGrant oAuthGrantPage;
-
- @Page
- private Applications applicationsPage;
-
- private static int TIME_SKEW_TOLERANCE = 3;
-
- public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
-
- @Deployment(name = JSConsoleTestApp.DEPLOYMENT_NAME)
- private static WebArchive jsConsoleTestApp() throws IOException {
- return exampleDeployment(JSConsoleTestApp.CLIENT_ID);
- }
-
- @Deployment(name = JSDatabaseTestApp.DEPLOYMENT_NAME)
- private static WebArchive jsDbApp() throws IOException {
- return exampleDeployment(JSDatabaseTestApp.CLIENT_ID);
- }
-
- @Override
- public void addAdapterTestRealms(List testRealms) {
- RealmRepresentation jsConsoleRealm = loadRealm(new File(TEST_APPS_HOME_DIR + "/js-console/example-realm.json"));
-
- fixClientUrisUsingDeploymentUrl(jsConsoleRealm,
- JSConsoleTestApp.CLIENT_ID, jsConsoleTestAppPage.buildUri().toASCIIString());
-
- jsConsoleRealm.setAccessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY); // seconds
-
- testRealms.add(jsConsoleRealm);
- }
-
- @Override
- public void setDefaultPageUriParameters() {
- super.setDefaultPageUriParameters();
- testRealmPage.setAuthRealm(EXAMPLE);
- }
-
- @Test
- public void testJSConsoleAuth() {
- jsConsoleTestAppPage.navigateTo();
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
-
- waitUntilElement(jsConsoleTestAppPage.getInitButtonElement()).is().present();
-
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.logIn();
- testRealmLoginPage.form().login("user", "invalid-password");
- assertCurrentUrlDoesntStartWith(jsConsoleTestAppPage);
-
- testRealmLoginPage.form().login("invalid-user", "password");
- assertCurrentUrlDoesntStartWith(jsConsoleTestAppPage);
-
- testRealmLoginPage.form().login("user", "password");
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
- jsConsoleTestAppPage.init();
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Success");
-
- jsConsoleTestAppPage.logOut();
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
- waitUntilElement(jsConsoleTestAppPage.getInitButtonElement()).is().present();
- jsConsoleTestAppPage.init();
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Not Authenticated)");
- }
-
- @Test
- public void testRefreshToken() {
- jsConsoleTestAppPage.navigateTo();
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
-
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.refreshToken();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Failed to refresh token");
-
- jsConsoleTestAppPage.logIn();
- testRealmLoginPage.form().login("user", "password");
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
- jsConsoleTestAppPage.init();
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Success");
-
- jsConsoleTestAppPage.refreshToken();
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Refresh Success");
- }
-
- @Test
- public void testRefreshTokenIfUnder30s() {
- jsConsoleTestAppPage.navigateTo();
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.refreshToken();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Failed to refresh token");
-
- jsConsoleTestAppPage.logIn();
- testRealmLoginPage.form().login("user", "password");
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
- jsConsoleTestAppPage.init();
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Success");
-
- jsConsoleTestAppPage.refreshTokenIfUnder30s();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Token not refreshed, valid for");
-
- pause((TOKEN_LIFESPAN_LEEWAY + 2) * 1000);
-
- jsConsoleTestAppPage.refreshTokenIfUnder30s();
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Refresh Success");
- }
-
- @Test
- public void testGetProfile() {
- jsConsoleTestAppPage.navigateTo();
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
-
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.getProfile();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Failed to load profile");
-
- jsConsoleTestAppPage.logIn();
- testRealmLoginPage.form().login("user", "password");
- assertCurrentUrlStartsWith(jsConsoleTestAppPage);
- jsConsoleTestAppPage.init();
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Success");
-
- jsConsoleTestAppPage.getProfile();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("\"username\": \"user\"");
- }
-
- @Test
- public void testCertEndpoint() {
- logInAndInit("standard");
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
-
- jsConsoleTestAppPage.sendCertRequest();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
- }
-
- @Test
- public void grantBrowserBasedApp() {
- testRealmPage.setAuthRealm(EXAMPLE);
- testRealmLoginPage.setAuthRealm(EXAMPLE);
- configPage.setConsoleRealm(EXAMPLE);
- loginEventsPage.setConsoleRealm(EXAMPLE);
- applicationsPage.setAuthRealm(EXAMPLE);
-
- jsConsoleTestAppPage.navigateTo();
- driver.manage().deleteAllCookies();
-
- ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "js-console");
- ClientRepresentation client = clientResource.toRepresentation();
- client.setConsentRequired(true);
- clientResource.update(client);
-
- RealmRepresentation realm = testRealmResource().toRepresentation();
- realm.setEventsEnabled(true);
- realm.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT", "LOGIN"));
- testRealmResource().update(realm);
-
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.logIn();
-
- testRealmLoginPage.form().login("user", "password");
-
- assertTrue(oAuthGrantPage.isCurrent());
- oAuthGrantPage.accept();
-
- jsConsoleTestAppPage.init();
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
-
- applicationsPage.navigateTo();
- applicationsPage.revokeGrantForApplication("js-console");
-
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setOnLoad("login-required");
- jsConsoleTestAppPage.init();
-
- waitUntilElement(By.tagName("body")).is().visible();
- assertTrue(oAuthGrantPage.isCurrent());
-
- loginEventsPage.navigateTo();
- loginPage.form().login(adminUser);
- loginEventsPage.table().filter();
- loginEventsPage.table().filterForm().addEventType("REVOKE_GRANT");
- loginEventsPage.table().update();
-
- List resultList = loginEventsPage.table().rows();
-
- assertEquals(1, resultList.size());
-
- resultList.get(0).findElement(By.xpath(".//td[text()='REVOKE_GRANT']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='account']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='revoked_client']/../td[text()='js-console']"));
-
- loginEventsPage.table().reset();
- loginEventsPage.table().filterForm().addEventType("LOGIN");
- loginEventsPage.table().update();
- resultList = loginEventsPage.table().rows();
-
- assertEquals(1, resultList.size());
-
- resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='js-console']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1' or text()='0:0:0:0:0:0:0:1']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='user']"));
- resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
- }
-
-
- @Test
- public void implicitFlowTest() {
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setFlow("implicit");
- jsConsoleTestAppPage.init();
-
- jsConsoleTestAppPage.logIn();
- assertResponseError("Implicit flow is disabled for the client");
-
- setImplicitFlowForClient();
-
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.logIn();
- assertResponseError("Standard flow is disabled for the client");
-
- logInAndInit("implicit");
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
- }
-
- @Test
- public void implicitFlowQueryTest() {
- setImplicitFlowForClient();
-
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setFlow("implicit");
- jsConsoleTestAppPage.setResponseMode("query");
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.logIn();
- assertResponseError("Response_mode 'query' not allowed");
- }
-
- @Test
- public void implicitFlowRefreshTokenTest() {
- setImplicitFlowForClient();
-
- logInAndInit("implicit");
-
- jsConsoleTestAppPage.refreshToken();
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Failed to refresh token");
- }
-
- @Test
- public void implicitFlowOnTokenExpireTest() {
- RealmRepresentation realm = testRealmResource().toRepresentation();
- realm.setAccessTokenLifespanForImplicitFlow(5);
- testRealmResource().update(realm);
-
- setImplicitFlowForClient();
-
- logInAndInit("implicit");
-
- pause(6000);
-
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Access token expired");
- }
-
- @Test
- public void implicitFlowCertEndpoint() {
- setImplicitFlowForClient();
- logInAndInit("implicit");
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
-
- jsConsoleTestAppPage.sendCertRequest();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
- }
-
- @Test
- public void testBearerRequest() {
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.createBearerRequest();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Unauthorized");
-
- logInAndInit("standard", "unauthorized");
- jsConsoleTestAppPage.createBearerRequest();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Forbidden");
-
- jsConsoleTestAppPage.logOut();
- logInAndInit("standard");
- jsConsoleTestAppPage.createBearerRequest();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("[\"Bill Burke\",\"Stian Thorgersen\",\"Stan Silvert\",\"Gabriel Cardoso\",\"Viliam Rockai\",\"Marek Posolda\",\"Boleslaw Dawidowicz\"]");
- }
-
- @Test
- public void loginRequiredAction() {
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setOnLoad("login-required");
- jsConsoleTestAppPage.init();
-
- assertCurrentUrlStartsWith(testRealmLoginPage);
- testRealmLoginPage.form().login("user", "password");
-
- waitUntilElement(jsConsoleTestAppPage.getInitButtonElement()).is().present();
- jsConsoleTestAppPage.init();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
- }
-
- @Test
- public void testUpdateToken() {
- logInAndInit("standard");
-
- jsConsoleTestAppPage.setTimeSkew(-33);
- setTimeOffset(33);
-
- jsConsoleTestAppPage.refreshTokenIfUnder5s();
-
- jsConsoleTestAppPage.setTimeSkew(-34);
- setTimeOffset(67);
-
- jsConsoleTestAppPage.refreshTokenIfUnder5s();
- jsConsoleTestAppPage.createBearerRequestToKeycloak();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Success");
- }
-
- @Test
- public void timeSkewTest() {
- logInAndInit("standard");
-
- jsConsoleTestAppPage.refreshTimeSkew();
-
- waitUntilElement(jsConsoleTestAppPage.getTimeSkewValue()).text().not().contains("undefined");
-
- int timeSkew = Integer.parseInt(jsConsoleTestAppPage.getTimeSkewValue().getText());
- assertTrue("TimeSkew was: " + timeSkew + ", but should be ~0", timeSkew >= 0 - TIME_SKEW_TOLERANCE);
- assertTrue("TimeSkew was: " + timeSkew + ", but should be ~0", timeSkew <= TIME_SKEW_TOLERANCE);
-
- setTimeOffset(40);
- jsConsoleTestAppPage.refreshToken();
- jsConsoleTestAppPage.refreshTimeSkew();
-
- waitUntilElement(jsConsoleTestAppPage.getTimeSkewValue()).text().not().contains("undefined");
-
- timeSkew = Integer.parseInt(jsConsoleTestAppPage.getTimeSkewValue().getText());
- assertTrue("TimeSkew was: " + timeSkew + ", but should be ~-40", timeSkew + 40 >= 0 - TIME_SKEW_TOLERANCE);
- assertTrue("TimeSkew was: " + timeSkew + ", but should be ~-40", timeSkew + 40 <= TIME_SKEW_TOLERANCE);
- }
-
- // KEYCLOAK-4179
- @Test
- public void testOneSecondTimeSkewTokenUpdate() {
- setTimeOffset(1);
-
- logInAndInit("standard");
-
- jsConsoleTestAppPage.refreshToken();
-
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Refresh Success");
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().not().contains("Failed to refresh token");
-
- try {
- // The events element should contain "Auth logout" but we need to wait for it
- // and text().not().contains() doesn't wait. With KEYCLOAK-4179 it took some time for "Auth Logout" to be present
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Logout");
-
- throw new RuntimeException("The events element shouldn't contain \"Auth Logout\" text");
- } catch (TimeoutException e) {
- // OK
- }
-
- }
-
- @Test
- public void testLocationHeaderInResponse() {
- logInAndInit("standard");
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
-
- jsConsoleTestAppPage.createUserRequest();
-
- UsersResource userResource = testRealmResource().users();
-
- List users = userResource.search("mhajas", 0, 1);
- assertEquals("There should be created user mhajas", 1, users.size());
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text()
- .contains("location: " + authServerContextRootPage.toString() + "/auth/admin/realms/" + EXAMPLE + "/users/" + users.get(0).getId());
- }
-
- @Test
- public void spaceInRealmNameTest() {
- String SPACE_REALM_NAME = "Example realm";
- adminClient.realm(EXAMPLE).update(RealmBuilder.edit(adminClient.realm(EXAMPLE).toRepresentation()).name(SPACE_REALM_NAME).build());
-
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setInput(SPACE_REALM_NAME);
- jsConsoleTestAppPage.initWithDifferentRealmName();
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Not Authenticated)");
- jsConsoleTestAppPage.logIn();
- waitUntilElement(By.xpath("//body")).is().present();
- testRealmLoginPage.form().login("user", "password");
- jsConsoleTestAppPage.setInput(SPACE_REALM_NAME);
- jsConsoleTestAppPage.initWithDifferentRealmName();
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Success");
- }
-
- @Test
- public void initializeWithTokenTest() {
- oauth.realm(EXAMPLE);
- oauth.clientId("js-console");
- oauth.redirectUri(appServerContextRootPage + "/js-console");
- oauth.doLogin("user", "password");
-
- String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
- OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
- String token = tokenResponse.getAccessToken();
- String refreshToken = tokenResponse.getRefreshToken();
-
- //String refreshToken = oauth.doRefreshTokenRequest(token, "password").getRefreshToken();
-
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setInput(token);
- jsConsoleTestAppPage.setInput2(refreshToken);
-
- jsConsoleTestAppPage.initWithBothTokens();
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
-
- jsConsoleTestAppPage.refreshToken();
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Refresh Success");
- }
-
- @Test
- public void initializeWithTimeSkew() {
- setTimeOffset(600);
- oauth.realm(EXAMPLE);
- oauth.clientId("js-console");
- oauth.redirectUri(appServerContextRootPage + "/js-console");
- oauth.doLogin("user", "password");
-
- String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
- OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
- String token = tokenResponse.getAccessToken();
- String refreshToken = tokenResponse.getRefreshToken();
-
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setInput(token);
- jsConsoleTestAppPage.setInput2(refreshToken);
- jsConsoleTestAppPage.setInput3("-600");
-
- jsConsoleTestAppPage.initWithTimeSkew();
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Authenticated)");
-
- jsConsoleTestAppPage.refreshToken();
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Auth Refresh Success");
-
- setTimeOffset(0);
- }
-
- @Test
- // KEYCLOAK-4503
- public void initializeWithRefreshToken() {
- oauth.realm(EXAMPLE);
- oauth.clientId("js-console");
- oauth.redirectUri("http://localhost:8280/js-console");
- oauth.doLogin("user", "password");
-
- String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
- String token = oauth.doAccessTokenRequest(code, "password").getAccessToken();
- String refreshToken = oauth.doRefreshTokenRequest(token, "password").getRefreshToken();
-
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setInput2(refreshToken);
-
- jsConsoleTestAppPage.initWithRefreshToken();
-
- waitUntilElement(jsConsoleTestAppPage.getOutputElement()).text().contains("Init Success (Not Authenticated)");
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().not().contains("Auth Success");
- }
-
- @Test
- public void reentrancyCallbackTest() {
- logInAndInit("standard");
-
- jsConsoleTestAppPage.callReentrancyCallback();
-
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("First callback");
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().contains("Second callback");
-
- waitUntilElement(jsConsoleTestAppPage.getEventsElement()).text().not().contains("Auth Logout");
- }
-
- private void setImplicitFlowForClient() {
- ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "js-console");
- ClientRepresentation client = clientResource.toRepresentation();
- client.setImplicitFlowEnabled(true);
- client.setStandardFlowEnabled(false);
- clientResource.update(client);
- }
-
- private void logInAndInit(String flow, String user) {
- jsConsoleTestAppPage.navigateTo();
- jsConsoleTestAppPage.setFlow(flow);
- jsConsoleTestAppPage.init();
- jsConsoleTestAppPage.logIn();
- waitUntilElement(By.xpath("//body")).is().present();
- testRealmLoginPage.form().login(user, "password");
- jsConsoleTestAppPage.setFlow(flow);
- jsConsoleTestAppPage.init();
- }
-
- private void logInAndInit(String flow) {
- logInAndInit(flow, "user");
- }
-
- private void assertResponseError(String errorDescription) {
- jsConsoleTestAppPage.showErrorResponse();
- assertThat(jsConsoleTestAppPage.getOutputElement().getText(), containsString(errorDescription));
- }
-
-}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/AbstractJavascriptTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/AbstractJavascriptTest.java
new file mode 100644
index 00000000000..fabedb8fe88
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/AbstractJavascriptTest.java
@@ -0,0 +1,180 @@
+package org.keycloak.testsuite.adapter.javascript;
+
+import org.jboss.arquillian.drone.api.annotation.Drone;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Before;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.AbstractAuthTest;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.login.OIDCLogin;
+import org.keycloak.testsuite.util.ClientBuilder;
+import org.keycloak.testsuite.util.JavascriptBrowser;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.RolesBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.collection.IsMapContaining.hasEntry;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
+import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
+
+/**
+ * @author mhajas
+ */
+public abstract class AbstractJavascriptTest extends AbstractAuthTest {
+
+ @FunctionalInterface
+ interface QuadFunction {
+ void apply(T a, U b, V c, W d);
+ }
+
+ public static final String CLIENT_ID = "js-console";
+ public static final String REALM_NAME = "test";
+ public static final String SPACE_REALM_NAME = "Example realm";
+ public static final String JAVASCRIPT_URL = "/auth/realms/" + REALM_NAME + "/testing/javascript";
+ public static final String JAVASCRIPT_ENCODED_SPACE_URL = "/auth/realms/Example%20realm/testing/javascript";
+ public static final String JAVASCRIPT_SPACE_URL = "/auth/realms/Example realm/testing/javascript";
+ public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
+
+
+ @Drone
+ @JavascriptBrowser
+ protected WebDriver jsDriver;
+
+ protected JavascriptExecutor jsExecutor;
+
+ @Page
+ @JavascriptBrowser
+ protected OIDCLogin testRealmLoginPage;
+
+ @FindBy(id = "output")
+ @JavascriptBrowser
+ protected WebElement outputArea;
+
+ @FindBy(id = "events")
+ @JavascriptBrowser
+ protected WebElement eventsArea;
+
+ public static final UserRepresentation testUser;
+ public static final UserRepresentation unauthorizedUser;
+
+ static {
+ testUser = UserBuilder.create().username("test-user@localhost").password("password").build();
+ unauthorizedUser = UserBuilder.create().username("unauthorized").password("password").build();
+ }
+
+
+ @Before
+ public void beforeJavascriptTest() {
+ jsExecutor = (JavascriptExecutor) jsDriver;
+ }
+
+ @Override
+ public void addTestRealms(List testRealms) {
+ testRealms.add(updateRealm(RealmBuilder.create()
+ .name(REALM_NAME)
+ .roles(
+ RolesBuilder.create()
+ .realmRole(new RoleRepresentation("user", "", false))
+ .realmRole(new RoleRepresentation("admin", "", false))
+ )
+ .user(
+ UserBuilder.create()
+ .username("test-user@localhost").password("password")
+ .addRoles("user")
+ .role("realm-management", "view-realm")
+ .role("realm-management", "manage-users")
+ .role("account", "view-profile")
+ .role("account", "manage-account")
+ )
+ .user(
+ UserBuilder.create()
+ .username("unauthorized").password("password")
+ )
+ .client(
+ ClientBuilder.create()
+ .clientId(CLIENT_ID)
+ .redirectUris(JAVASCRIPT_URL + "/*", JAVASCRIPT_ENCODED_SPACE_URL + "/*")
+ .publicClient()
+ )
+ .accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY)
+ .testEventListener()
+ ));
+ }
+
+ protected JavascriptStateValidator buildFunction(QuadFunction f, T x) {
+ return (y,z,w) -> f.apply(x, y, z, w);
+ }
+
+ protected void setImplicitFlowForClient() {
+ ClientResource clientResource = ApiUtil.findClientResourceByClientId(adminClient.realms().realm(REALM_NAME), CLIENT_ID);
+ ClientRepresentation client = clientResource.toRepresentation();
+ client.setImplicitFlowEnabled(true);
+ client.setStandardFlowEnabled(false);
+ clientResource.update(client);
+ }
+
+ protected void setStandardFlowForClient() {
+ ClientResource clientResource = ApiUtil.findClientResourceByClientId(adminClient.realms().realm(REALM_NAME), CLIENT_ID);
+ ClientRepresentation client = clientResource.toRepresentation();
+ client.setImplicitFlowEnabled(false);
+ client.setStandardFlowEnabled(true);
+ clientResource.update(client);
+ }
+
+ protected abstract RealmRepresentation updateRealm(RealmBuilder builder);
+
+ protected void assertSuccessfullyLoggedIn(WebDriver driver1, Object output, WebElement events) {
+ buildFunction(this::assertOutputContains, "Init Success (Authenticated)").validate(driver1, output, events);
+ waitUntilElement(events).text().contains("Auth Success");
+ }
+
+ protected void assertInitNotAuth(WebDriver driver1, Object output, WebElement events) {
+ buildFunction(this::assertOutputContains, "Init Success (Not Authenticated)").validate(driver1, output, events);
+ }
+
+ protected void assertOnLoginPage(WebDriver driver1, Object output, WebElement events) {
+ waitUntilElement(By.tagName("body")).is().present();
+ assertCurrentUrlStartsWith(testRealmLoginPage, driver1);
+ }
+
+ public void assertOutputWebElementContains(String value, WebDriver driver1, Object output, WebElement events) {
+ waitUntilElement((WebElement) output).text().contains(value);
+ }
+
+ public void assertOutputContains(String value, WebDriver driver1, Object output, WebElement events) {
+ if (output instanceof WebElement) {
+ waitUntilElement((WebElement) output).text().contains(value);
+ } else {
+ Assert.assertThat((String) output, containsString(value));
+ }
+ }
+
+ public void assertEventsWebElementContains(String value, WebDriver driver1, Object output, WebElement events) {
+ waitUntilElement(events).text().contains(value);
+ }
+
+ public ResponseValidator assertResponseStatus(long status) {
+ return output -> Assert.assertThat(output, hasEntry("status", status));
+ }
+
+ public JavascriptStateValidator assertOutputContains(String text) {
+ return buildFunction(this::assertOutputContains, text);
+ }
+
+ public JavascriptStateValidator assertEventsContains(String text) {
+ return buildFunction(this::assertEventsWebElementContains, text);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JSObjectBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JSObjectBuilder.java
new file mode 100644
index 00000000000..f0d549b7d50
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JSObjectBuilder.java
@@ -0,0 +1,90 @@
+package org.keycloak.testsuite.adapter.javascript;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author mhajas
+ */
+public class JSObjectBuilder {
+
+ private Map arguments;
+
+
+ public static JSObjectBuilder create() {
+ return new JSObjectBuilder();
+ }
+
+ private JSObjectBuilder() {
+ arguments = new HashMap<>();
+ }
+
+ public JSObjectBuilder defaultSettings() {
+ standardFlow();
+ fragmentResponse();
+ return this;
+ }
+
+ public JSObjectBuilder standardFlow() {
+ arguments.put("flow", "standard");
+ return this;
+ }
+
+ public JSObjectBuilder implicitFlow() {
+ arguments.put("flow", "implicit");
+ return this;
+ }
+
+ public JSObjectBuilder fragmentResponse() {
+ arguments.put("responseMode", "fragment");
+ return this;
+ }
+
+ public JSObjectBuilder queryResponse() {
+ arguments.put("responseMode", "query");
+ return this;
+ }
+
+ public JSObjectBuilder checkSSOOnLoad() {
+ arguments.put("onLoad", "check-sso");
+ return this;
+ }
+
+ public JSObjectBuilder loginRequiredOnLoad() {
+ arguments.put("onLoad", "login-required");
+ return this;
+ }
+
+ public JSObjectBuilder add(String key, Object value) {
+ arguments.put(key, value);
+ return this;
+ }
+
+ public boolean isLoginRequired() {
+ return arguments.get("onLoad").equals("login-required");
+ }
+
+
+ public String build() {
+ StringBuilder argument = new StringBuilder("{");
+ String comma = "";
+ for (Map.Entry option : arguments.entrySet()) {
+ argument.append(comma)
+ .append(option.getKey())
+ .append(" : ");
+
+ if (!(option.getValue() instanceof Integer)) argument.append("\"");
+
+ argument.append(option.getValue());
+
+ if (!(option.getValue() instanceof Integer)) argument.append("\"");
+ comma = ",";
+ }
+
+ argument.append("}");
+
+ return argument.toString();
+ }
+
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptAdapterTest.java
new file mode 100644
index 00000000000..10361a6038c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptAdapterTest.java
@@ -0,0 +1,517 @@
+package org.keycloak.testsuite.adapter.javascript;
+
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.keycloak.OAuth2Constants;
+import org.keycloak.admin.client.resource.ClientResource;
+import org.keycloak.events.Details;
+import org.keycloak.events.EventType;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.auth.page.account.Applications;
+import org.keycloak.testsuite.auth.page.login.OAuthGrant;
+import org.keycloak.testsuite.util.JavascriptBrowser;
+import org.keycloak.testsuite.util.OAuthClient;
+import org.keycloak.testsuite.util.RealmBuilder;
+import org.keycloak.testsuite.util.UserBuilder;
+import org.openqa.selenium.TimeoutException;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebDriverException;
+import org.openqa.selenium.WebElement;
+
+import java.net.MalformedURLException;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.Math.toIntExact;
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.collection.IsMapContaining.hasEntry;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith;
+import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith;
+import static org.keycloak.testsuite.util.WaitUtils.waitUntilElement;
+
+/**
+ * @author mhajas
+ */
+public class JavascriptAdapterTest extends AbstractJavascriptTest {
+
+ private String testAppUrl;
+ private JavascriptTestExecutor testExecutor;
+ private static int TIME_SKEW_TOLERANCE = 3;
+
+ @Rule
+ public AssertEvents events = new AssertEvents(this);
+
+ @Page
+ @JavascriptBrowser
+ private Applications applicationsPage;
+
+ @Page
+ @JavascriptBrowser
+ private OAuthGrant oAuthGrantPage;
+
+ @Override
+ protected RealmRepresentation updateRealm(RealmBuilder builder) {
+ return builder.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY).build();
+ }
+
+ @Before
+ public void setDefaultEnvironment() {
+ testAppUrl = authServerContextRootPage + JAVASCRIPT_URL + "/index.html";
+
+ testRealmLoginPage.setAuthRealm(REALM_NAME);
+ oAuthGrantPage.setAuthRealm(REALM_NAME);
+ applicationsPage.setAuthRealm(REALM_NAME);
+
+ jsDriver.navigate().to(testAppUrl);
+ testExecutor = JavascriptTestExecutor.create(jsDriver, testRealmLoginPage);
+
+ waitUntilElement(outputArea).is().present();
+ assertCurrentUrlStartsWith(testAppUrl, jsDriver);
+
+ jsDriver.manage().deleteAllCookies();
+ }
+
+ private JSObjectBuilder defaultArguments() {
+ return JSObjectBuilder.create().defaultSettings();
+ }
+
+ private void assertOnTestAppUrl(WebDriver jsDriver, Object output, WebElement events) {
+ assertCurrentUrlStartsWith(testAppUrl, jsDriver);
+ }
+
+ @Test
+ public void testJSConsoleAuth() {
+ testExecutor.init(defaultArguments(), this::assertInitNotAuth)
+ .login(this::assertOnLoginPage)
+ .loginForm( UserBuilder.create().username("user").password("invalid-password").build(),
+ (driver1, output, events) -> assertCurrentUrlDoesntStartWith(testAppUrl, driver1))
+ .loginForm(UserBuilder.create().username("invalid-user").password("password").build(),
+ (driver1, output, events) -> assertCurrentUrlDoesntStartWith(testAppUrl, driver1))
+ .loginForm(testUser, this::assertOnTestAppUrl)
+ .init(defaultArguments(), this::assertSuccessfullyLoggedIn)
+ .logout(this::assertOnTestAppUrl)
+ .init(defaultArguments(), this::assertInitNotAuth);
+ }
+
+ @Test
+ public void testRefreshToken() {
+ testExecutor.init(defaultArguments(), this::assertInitNotAuth)
+ .refreshToken(9999, assertOutputContains("Failed to refresh token"))
+ .login(this::assertOnLoginPage)
+ .loginForm(testUser, this::assertOnTestAppUrl)
+ .init(defaultArguments(), this::assertSuccessfullyLoggedIn)
+ .refreshToken(9999, assertEventsContains("Auth Refresh Success"));
+ }
+
+ @Test
+ public void testRefreshTokenIfUnder30s() {
+ testExecutor.init(defaultArguments(), this::assertInitNotAuth)
+ .login(this::assertOnLoginPage)
+ .loginForm(testUser, this::assertOnTestAppUrl)
+ .init(defaultArguments(), this::assertSuccessfullyLoggedIn)
+ .refreshToken(30, assertOutputContains("Token not refreshed, valid for"))
+ .addTimeSkew(-5) // instead of wait move in time
+ .refreshToken(30, assertEventsContains("Auth Refresh Success"));
+ }
+
+ @Test
+ public void testGetProfile() {
+ testExecutor.init(defaultArguments(), this::assertInitNotAuth)
+ .getProfile(assertOutputContains("Failed to load profile"))
+ .login(this::assertOnLoginPage)
+ .loginForm(testUser, this::assertOnTestAppUrl)
+ .init(defaultArguments(), this::assertSuccessfullyLoggedIn)
+ .getProfile((driver1, output, events) -> Assert.assertThat((Map) output, hasEntry("username", testUser.getUsername())));
+ }
+
+ @Test
+ public void grantBrowserBasedApp() {
+ ClientResource clientResource = ApiUtil.findClientResourceByClientId(adminClient.realm(REALM_NAME), CLIENT_ID);
+ ClientRepresentation client = clientResource.toRepresentation();
+ client.setConsentRequired(true);
+ clientResource.update(client);
+
+ testExecutor.init(defaultArguments(), this::assertInitNotAuth)
+ .login(this::assertOnLoginPage)
+ .loginForm(testUser, (driver1, output, events) -> assertTrue(oAuthGrantPage.isCurrent(driver1))
+ // I am not sure why is this driver1 argument to isCurrent necessary, but I got exception without it
+ );
+
+ oAuthGrantPage.accept();
+
+ EventRepresentation loginEvent = events.expectLogin()
+ .client(CLIENT_ID)
+ .detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
+ .detail(Details.REDIRECT_URI, testAppUrl)
+ .detail(Details.USERNAME, testUser.getUsername())
+ .assertEvent();
+ String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+ testExecutor.init(defaultArguments(), this::assertSuccessfullyLoggedIn);
+
+ applicationsPage.navigateTo();
+ events.expectCodeToToken(codeId, loginEvent.getSessionId()).client(CLIENT_ID).assertEvent();
+
+ applicationsPage.revokeGrantForApplication(CLIENT_ID);
+ events.expect(EventType.REVOKE_GRANT)
+ .client("account")
+ .detail(Details.REVOKED_CLIENT, CLIENT_ID)
+ .assertEvent();
+
+ jsDriver.navigate().to(testAppUrl);
+ testExecutor.configure() // need to configure because we refreshed page
+ .init(defaultArguments(), this::assertInitNotAuth)
+ .login((driver1, output, events) -> assertTrue(oAuthGrantPage.isCurrent(driver1)));
+
+ // Clean
+ client.setConsentRequired(false);
+ clientResource.update(client);
+ }
+
+ @Test
+ public void implicitFlowTest() {
+ testExecutor.init(defaultArguments().implicitFlow(), this::assertInitNotAuth)
+ .login(this::assertOnTestAppUrl)
+ .errorResponse(assertOutputContains("Implicit flow is disabled for the client"));
+
+ setImplicitFlowForClient();
+ jsDriver.navigate().to(testAppUrl);
+
+ testExecutor.init(defaultArguments(), this::assertInitNotAuth)
+ .login(this::assertOnTestAppUrl)
+ .errorResponse(assertOutputContains("Standard flow is disabled for the client"));
+ jsDriver.navigate().to(testAppUrl);
+
+ testExecutor.init(defaultArguments().implicitFlow(), this::assertInitNotAuth)
+ .login(this::assertOnLoginPage)
+ .loginForm(testUser, this::assertOnTestAppUrl)
+ .init(defaultArguments().implicitFlow(), this::assertSuccessfullyLoggedIn);
+
+ setStandardFlowForClient();
+ }
+
+ @Test
+ public void testCertEndpoint() {
+ testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
+ .sendXMLHttpRequest(XMLHttpRequest.create()
+ .url(authServerContextRootPage + "/auth/realms/" + REALM_NAME + "/protocol/openid-connect/certs")
+ .method("GET")
+ .addHeader("Accept", "application/json")
+ .addHeader("Authorization", "Bearer ' + keycloak.token + '"),
+ assertResponseStatus(200));
+ }
+
+ @Test
+ public void implicitFlowQueryTest() {
+ setImplicitFlowForClient();
+ testExecutor.init(defaultArguments().implicitFlow().queryResponse(), this::assertInitNotAuth)
+ .login(((driver1, output, events) ->
+ Assert.assertThat(driver1.getCurrentUrl(), containsString("Response_mode+%27query%27+not+allowed"))));
+ setStandardFlowForClient();
+ }
+
+ @Test
+ public void implicitFlowRefreshTokenTest() {
+ setImplicitFlowForClient();
+ testExecutor.logInAndInit(defaultArguments().implicitFlow(), testUser, this::assertSuccessfullyLoggedIn)
+ .refreshToken(9999, assertOutputContains("Failed to refresh token"));
+ setStandardFlowForClient();
+ }
+
+ @Test
+ public void implicitFlowOnTokenExpireTest() {
+ RealmRepresentation realm = adminClient.realms().realm(REALM_NAME).toRepresentation();
+ Integer storeAccesTokenLifespan = realm.getAccessTokenLifespanForImplicitFlow();
+ realm.setAccessTokenLifespanForImplicitFlow(5);
+ adminClient.realms().realm(REALM_NAME).update(realm);
+
+ setImplicitFlowForClient();
+ testExecutor.logInAndInit(defaultArguments().implicitFlow(), testUser, this::assertSuccessfullyLoggedIn)
+ .addTimeSkew(-5); // Move in time instead of wait
+
+ waitUntilElement(eventsArea).text().contains("Access token expired");
+
+ // Get to origin state
+ realm.setAccessTokenLifespanForImplicitFlow(storeAccesTokenLifespan);
+ adminClient.realms().realm(REALM_NAME).update(realm);
+ setStandardFlowForClient();
+ }
+
+ @Test
+ public void implicitFlowCertEndpoint() {
+ setImplicitFlowForClient();
+ testExecutor.logInAndInit(defaultArguments().implicitFlow(), testUser, this::assertSuccessfullyLoggedIn)
+ .sendXMLHttpRequest(XMLHttpRequest.create()
+ .url(authServerContextRootPage + "/auth/realms/" + REALM_NAME + "/protocol/openid-connect/certs")
+ .method("GET")
+ .addHeader("Accept", "application/json")
+ .addHeader("Authorization", "Bearer ' + keycloak.token + '"),
+ assertResponseStatus(200));
+ setStandardFlowForClient();
+ }
+
+ @Test
+ public void testBearerRequest() {
+ XMLHttpRequest request = XMLHttpRequest.create()
+ .url(authServerContextRootPage + "/auth/admin/realms/" + REALM_NAME + "/roles")
+ .method("GET")
+ .addHeader("Accept", "application/json")
+ .addHeader("Authorization", "Bearer ' + keycloak.token + '");
+
+ testExecutor.init(defaultArguments())
+ .sendXMLHttpRequest(request, assertResponseStatus(401))
+ .refresh();
+ if (!"phantomjs".equals(System.getProperty("js.browser"))) {
+ // I have no idea why, but this request doesn't work with phantomjs, it works in chrome
+ testExecutor.logInAndInit(defaultArguments(), unauthorizedUser, this::assertSuccessfullyLoggedIn)
+ .sendXMLHttpRequest(request, output -> Assert.assertThat(output, hasEntry("status", 403L)))
+ .logout(this::assertOnTestAppUrl)
+ .refresh();
+ }
+ testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
+ .sendXMLHttpRequest(request, assertResponseStatus(200));
+ }
+
+ @Test
+ public void loginRequiredAction() {
+ try {
+ testExecutor.init(defaultArguments().loginRequiredOnLoad());
+ // This throws exception because when JavascriptExecutor waits for AsyncScript to finish
+ // it is redirected to login page and executor gets no response
+
+ throw new RuntimeException("Probably the login-required OnLoad mode doesn't work, because testExecutor should fail with error that page was redirected.");
+ } catch (WebDriverException ex) {
+ // should happen
+ }
+
+ testExecutor.loginForm(testUser, this::assertOnTestAppUrl)
+ .init(defaultArguments(), this::assertSuccessfullyLoggedIn);
+ }
+
+ @Test
+ public void testUpdateToken() {
+ XMLHttpRequest request = XMLHttpRequest.create()
+ .url(authServerContextRootPage + "/auth/admin/realms/" + REALM_NAME + "/roles")
+ .method("GET")
+ .addHeader("Accept", "application/json")
+ .addHeader("Authorization", "Bearer ' + keycloak.token + '");
+
+ testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
+ .addTimeSkew(-33);
+ setTimeOffset(33);
+ testExecutor.refreshToken(5, assertEventsContains("Auth Refresh Success"));
+
+ setTimeOffset(67);
+ testExecutor.addTimeSkew(-34)
+ .sendXMLHttpRequest(request, assertResponseStatus(401))
+ .refreshToken(5, assertEventsContains("Auth Refresh Success"))
+ .sendXMLHttpRequest(request, assertResponseStatus(200));
+
+ setTimeOffset(0);
+ }
+
+ @Test
+ public void timeSkewTest() {
+ testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
+ .checkTimeSkew((driver1, output, events) -> assertThat(toIntExact((long) output),
+ is(
+ both(greaterThan(0 - TIME_SKEW_TOLERANCE))
+ .and(lessThan(TIME_SKEW_TOLERANCE))
+ )
+ ));
+
+ setTimeOffset(40);
+
+ testExecutor.refreshToken(9999, assertEventsContains("Auth Refresh Success"))
+ .checkTimeSkew((driver1, output, events) -> assertThat(toIntExact((long) output),
+ is(
+ both(greaterThan(-40 - TIME_SKEW_TOLERANCE))
+ .and(lessThan(-40 + TIME_SKEW_TOLERANCE))
+ )
+ ));
+ }
+
+ @Test
+ public void testOneSecondTimeSkewTokenUpdate() {
+ setTimeOffset(1);
+
+ testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
+ .refreshToken(9999, assertEventsContains("Auth Refresh Success"));
+
+ try {
+ // The events element should contain "Auth logout" but we need to wait for it
+ // and text().not().contains() doesn't wait. With KEYCLOAK-4179 it took some time for "Auth Logout" to be present
+ waitUntilElement(eventsArea).text().contains("Auth Logout");
+
+ throw new RuntimeException("The events element shouldn't contain \"Auth Logout\" text");
+ } catch (TimeoutException e) {
+ // OK
+ }
+ }
+
+ @Test
+ public void testLocationHeaderInResponse() {
+ XMLHttpRequest request = XMLHttpRequest.create()
+ .url(authServerContextRootPage + "/auth/admin/realms/" + REALM_NAME + "/users")
+ .method("POST")
+ .content("JSON.stringify(JSON.parse('{\"emailVerified\" : false, \"enabled\" : true, \"username\": \"mhajas\", \"firstName\" :\"First\", \"lastName\":\"Last\",\"email\":\"email@redhat.com\", \"attributes\": {}}'))")
+ .addHeader("Accept", "application/json")
+ .addHeader("Authorization", "Bearer ' + keycloak.token + '")
+ .addHeader("Content-Type", "application/json; charset=UTF-8");
+
+ testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
+ .sendXMLHttpRequest(request, response -> {
+ List users = adminClient.realm(REALM_NAME).users().search("mhajas", 0, 1);
+ assertEquals("There should be created user mhajas", 1, users.size());
+
+ assertThat((String) response.get("responseHeaders"), containsString("location: " + authServerContextRootPage.toString() + "/auth/admin/realms/" + REALM_NAME + "/users/" + users.get(0).getId()));
+ });
+ }
+
+ @Test
+ public void spaceInRealmNameTest() {
+ // Unfortunately this test doesn't work on phantomjs
+ // it looks like phantomjs double encode %20 => %25%20
+ Assume.assumeTrue("This test doesn't work with phantomjs", !"phantomjs".equals(System.getProperty("js.browser")));
+
+ adminClient.realm(REALM_NAME).update(RealmBuilder.edit(adminClient.realm(REALM_NAME).toRepresentation()).name(SPACE_REALM_NAME).build());
+
+ JSObjectBuilder configuration = JSObjectBuilder.create()
+ .add("url", authServerContextRootPage + "/auth")
+ .add("realm", SPACE_REALM_NAME)
+ .add("clientId", CLIENT_ID);
+
+ testAppUrl = authServerContextRootPage + JAVASCRIPT_SPACE_URL + "/index.html";
+ jsDriver.navigate().to(testAppUrl);
+ testRealmLoginPage.setAuthRealm(SPACE_REALM_NAME);
+
+ testExecutor.configure(configuration)
+ .init(defaultArguments(), this::assertInitNotAuth)
+ .login(this::assertOnLoginPage)
+ .loginForm(testUser, this::assertOnTestAppUrl)
+ .configure(configuration)
+ .init(defaultArguments(), this::assertSuccessfullyLoggedIn);
+
+ // Clean
+ adminClient.realm(SPACE_REALM_NAME).update(RealmBuilder.edit(adminClient.realm(SPACE_REALM_NAME).toRepresentation()).name(REALM_NAME).build());
+ testRealmLoginPage.setAuthRealm(REALM_NAME);
+ }
+
+ @Test
+ public void initializeWithTokenTest() {
+ oauth.setDriver(jsDriver);
+
+ oauth.realm(REALM_NAME);
+ oauth.clientId(CLIENT_ID);
+ oauth.redirectUri(testAppUrl);
+ oauth.doLogin(testUser);
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+ String token = tokenResponse.getAccessToken();
+ String refreshToken = tokenResponse.getRefreshToken();
+
+ testExecutor.init(JSObjectBuilder.create()
+ .add("token", token)
+ .add("refreshToken", refreshToken)
+ , this::assertSuccessfullyLoggedIn)
+ .refreshToken(9999, assertEventsContains("Auth Refresh Success"));
+
+
+ oauth.setDriver(driver);
+ }
+
+ @Test
+ public void initializeWithTimeSkew() {
+ oauth.setDriver(jsDriver); // Oauth need to login with jsDriver
+
+ // Get access token and refresh token to initialize with
+ setTimeOffset(600);
+ oauth.realm(REALM_NAME);
+ oauth.clientId(CLIENT_ID);
+ oauth.redirectUri(testAppUrl);
+ oauth.doLogin(testUser);
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+ String token = tokenResponse.getAccessToken();
+ String refreshToken = tokenResponse.getRefreshToken();
+
+ // Perform test
+ testExecutor.init(JSObjectBuilder.create()
+ .add("token", token)
+ .add("refreshToken", refreshToken)
+ .add("timeSkew", -600)
+ , this::assertSuccessfullyLoggedIn)
+ .checkTimeSkew((driver1, output, events) -> assertThat(output, equalTo(-600L)))
+ .refreshToken(9999, assertEventsContains("Auth Refresh Success"))
+ .checkTimeSkew((driver1, output, events) -> assertThat(output, equalTo(-600L)));
+
+ setTimeOffset(0);
+
+ oauth.setDriver(driver); // Clean
+ }
+
+ @Test
+ // KEYCLOAK-4503
+ public void initializeWithRefreshToken() {
+ oauth.setDriver(jsDriver); // Oauth need to login with jsDriver
+
+ oauth.realm(REALM_NAME);
+ oauth.clientId(CLIENT_ID);
+ oauth.redirectUri(testAppUrl);
+ oauth.doLogin(testUser);
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+ String token = tokenResponse.getAccessToken();
+ String refreshToken = tokenResponse.getRefreshToken();
+
+ testExecutor.init(JSObjectBuilder.create()
+ .add("refreshToken", refreshToken)
+ , (driver1, output, events) -> {
+ assertInitNotAuth(driver1, output, events);
+ waitUntilElement(events).text().not().contains("Auth Success");
+ });
+
+ oauth.setDriver(driver); // Clean
+ }
+
+ @Test
+ public void reentrancyCallbackTest() {
+ testExecutor.logInAndInit(defaultArguments(), testUser, this::assertSuccessfullyLoggedIn)
+ .executeAsyncScript(
+ "var callback = arguments[arguments.length - 1];" +
+ "keycloak.updateToken(60).success(function () {" +
+ " event(\"First callback\");" +
+ " keycloak.updateToken(60).success(function () {" +
+ " event(\"Second callback\");" +
+ " callback(\"Success\");" +
+ " });" +
+ " }" +
+ ");"
+ , (driver1, output, events) -> {
+ waitUntilElement(events).text().contains("First callback");
+ waitUntilElement(events).text().contains("Second callback");
+ waitUntilElement(events).text().not().contains("Auth Logout");
+ }
+ );
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptStateValidator.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptStateValidator.java
new file mode 100644
index 00000000000..3c9f1ea3505
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptStateValidator.java
@@ -0,0 +1,15 @@
+package org.keycloak.testsuite.adapter.javascript;
+
+import org.keycloak.models.KeycloakSession;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.io.Serializable;
+
+/**
+ * @author mhajas
+ */
+public interface JavascriptStateValidator extends Serializable {
+
+ void validate(WebDriver driver, Object output, WebElement events);
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptTestExecutor.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptTestExecutor.java
new file mode 100644
index 00000000000..8db7dee03f5
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/JavascriptTestExecutor.java
@@ -0,0 +1,251 @@
+package org.keycloak.testsuite.adapter.javascript;
+
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.auth.page.login.OIDCLogin;
+import org.openqa.selenium.By;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * @author mhajas
+ */
+public class JavascriptTestExecutor {
+ private WebDriver jsDriver;
+ private JavascriptExecutor jsExecutor;
+ private WebElement output;
+ private WebElement events;
+ private OIDCLogin loginPage;
+ private boolean configured;
+
+ public static JavascriptTestExecutor create(WebDriver driver, OIDCLogin loginPage) {
+ return new JavascriptTestExecutor(driver, loginPage);
+ }
+
+ private JavascriptTestExecutor(WebDriver driver, OIDCLogin loginPage) {
+ this.jsDriver = driver;
+ driver.manage().timeouts().setScriptTimeout(10, TimeUnit.SECONDS);
+ jsExecutor = (JavascriptExecutor) driver;
+ events = driver.findElement(By.id("events"));
+ output = driver.findElement(By.id("output"));
+ this.loginPage = loginPage;
+ configured = false;
+ }
+
+ public JavascriptTestExecutor login() {
+ return login(null);
+ }
+
+ public JavascriptTestExecutor login(JavascriptStateValidator validator) {
+ jsExecutor.executeScript("keycloak.login()");
+
+ if (validator != null) {
+ validator.validate(jsDriver, output, events);
+ }
+
+ configured = false; // Getting out of testApp page => loosing keycloak variable etc.
+
+ return this;
+ }
+
+ public JavascriptTestExecutor loginForm(UserRepresentation user) {
+ return loginForm(user, null);
+ }
+
+ public JavascriptTestExecutor loginForm(UserRepresentation user, JavascriptStateValidator validator) {
+ loginPage.form().login(user);
+
+ if (validator != null) {
+ validator.validate(jsDriver, null, events);
+ }
+
+ configured = false; // Getting out of testApp page => loosing keycloak variable etc.
+ // this is necessary in case we skipped login button for example in login-required mode
+
+ return this;
+ }
+
+ public JavascriptTestExecutor logout() {
+ return logout(null);
+ }
+
+ public JavascriptTestExecutor logout(JavascriptStateValidator validator) {
+ jsExecutor.executeScript("keycloak.logout()");
+ if (validator != null) {
+ validator.validate(jsDriver, output, events);
+ }
+
+ configured = false; // Loosing keycloak variable so we need to create it when init next session
+
+ return this;
+ }
+
+ public JavascriptTestExecutor configure() {
+ return configure(null);
+ }
+
+ public JavascriptTestExecutor configure(JSObjectBuilder argumentsBuilder) {
+ if (argumentsBuilder == null) {
+ jsExecutor.executeScript("keycloak = Keycloak()");
+ } else {
+ String configArguments = argumentsBuilder.build();
+ jsExecutor.executeScript("keycloak = Keycloak(" + configArguments + ")");
+ }
+
+ jsExecutor.executeScript("keycloak.onAuthSuccess = function () {event('Auth Success')}"); // event function is declared in index.html
+ jsExecutor.executeScript("keycloak.onAuthError = function () {event('Auth Error')}");
+ jsExecutor.executeScript("keycloak.onAuthRefreshSuccess = function () {event('Auth Refresh Success')}");
+ jsExecutor.executeScript("keycloak.onAuthRefreshError = function () {event('Auth Refresh Error')}");
+ jsExecutor.executeScript("keycloak.onAuthLogout = function () {event('Auth Logout')}");
+ jsExecutor.executeScript("keycloak.onTokenExpired = function () {event('Access token expired.')}");
+
+ configured = true;
+
+ return this;
+ }
+
+ public JavascriptTestExecutor init(JSObjectBuilder argumentsBuilder) {
+ return init(argumentsBuilder, null);
+ }
+
+ public JavascriptTestExecutor init(JSObjectBuilder argumentsBuilder, JavascriptStateValidator validator) {
+ if(!configured) {
+ configure();
+ }
+
+ String arguments = argumentsBuilder.build();
+
+ Object output = jsExecutor.executeAsyncScript(
+ "var callback = arguments[arguments.length - 1];" +
+ " keycloak.init(" + arguments + ").success(function (authenticated) {" +
+ " callback(\"Init Success (\" + (authenticated ? \"Authenticated\" : \"Not Authenticated\") + \")\");" +
+ " }).error(function () {" +
+ " callback(\"Init Error\");" +
+ " });");
+
+ if (validator != null) {
+ validator.validate(jsDriver, output, events);
+ }
+
+ return this;
+ }
+
+ public JavascriptTestExecutor logInAndInit(JSObjectBuilder argumentsBuilder,
+ UserRepresentation user, JavascriptStateValidator validator) {
+ init(argumentsBuilder);
+ login();
+ loginForm(user);
+ init(argumentsBuilder, validator);
+ return this;
+ }
+
+ public JavascriptTestExecutor refreshToken(int value) {
+ return refreshToken(value, null);
+ }
+
+ public JavascriptTestExecutor refreshToken(int value, JavascriptStateValidator validator) {
+ Object output = jsExecutor.executeAsyncScript(
+ "var callback = arguments[arguments.length - 1];" +
+ " keycloak.updateToken(" + Integer.toString(value) + ").success(function (refreshed) {" +
+ " if (refreshed) {" +
+ " callback(keycloak.tokenParsed);" +
+ " } else {" +
+ " callback('Token not refreshed, valid for ' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds');" +
+ " }" +
+ " }).error(function () {" +
+ " callback('Failed to refresh token');" +
+ " });");
+
+ if(validator != null) {
+ validator.validate(jsDriver, output, events);
+ }
+
+ return this;
+ }
+
+ public JavascriptTestExecutor getProfile() {
+ return getProfile(null);
+ }
+
+ public JavascriptTestExecutor getProfile(JavascriptStateValidator validator) {
+
+ Object output = jsExecutor.executeAsyncScript(
+ "var callback = arguments[arguments.length - 1];" +
+ " keycloak.loadUserProfile().success(function (profile) {" +
+ " callback(profile);" +
+ " }).error(function () {" +
+ " callback('Failed to load profile');" +
+ " });");
+
+ if(validator != null) {
+ validator.validate(jsDriver, output, events);
+ }
+ return this;
+ }
+
+ public JavascriptTestExecutor sendXMLHttpRequest(XMLHttpRequest request, ResponseValidator validator) {
+ validator.validate(request.send(jsExecutor));
+
+ return this;
+ }
+
+ public JavascriptTestExecutor refresh() {
+ jsDriver.navigate().refresh();
+ configured = false; // Refreshing webpage => Loosing keycloak variable
+
+ return this;
+ }
+
+ public JavascriptTestExecutor addTimeSkew(int addition) {
+ jsExecutor.executeScript("keycloak.timeSkew += " + Integer.toString(addition));
+
+ return this;
+ }
+
+ public JavascriptTestExecutor checkTimeSkew(JavascriptStateValidator validator) {
+ Object timeSkew = jsExecutor.executeScript("return keycloak.timeSkew");
+
+ validator.validate(jsDriver, timeSkew, events);
+
+ return this;
+ }
+
+ public JavascriptTestExecutor executeScript(String script) {
+ return executeScript(script, null);
+ }
+
+ public JavascriptTestExecutor executeScript(String script, JavascriptStateValidator validator) {
+ Object output = jsExecutor.executeScript(script);
+
+ if(validator != null) {
+ validator.validate(jsDriver, output, events);
+ }
+
+ return this;
+ }
+
+ public JavascriptTestExecutor executeAsyncScript(String script) {
+ return executeAsyncScript(script, null);
+ }
+
+ public JavascriptTestExecutor executeAsyncScript(String script, JavascriptStateValidator validator) {
+ Object output = jsExecutor.executeAsyncScript(script);
+
+ if(validator != null) {
+ validator.validate(jsDriver, output, events);
+ }
+
+ return this;
+ }
+
+ public JavascriptTestExecutor errorResponse(JavascriptStateValidator validator) {
+ Object output = jsExecutor.executeScript("return \"Error: \" + getParameterByName(\"error\") + \"\\n\" + \"Error description: \" + getParameterByName(\"error_description\")");
+
+ validator.validate(jsDriver, output, events);
+ return this;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/ResponseValidator.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/ResponseValidator.java
new file mode 100644
index 00000000000..8587d25e56b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/ResponseValidator.java
@@ -0,0 +1,15 @@
+package org.keycloak.testsuite.adapter.javascript;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author mhajas
+ */
+public interface ResponseValidator extends Serializable {
+
+ void validate(Map response);
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/XMLHttpRequest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/XMLHttpRequest.java
new file mode 100644
index 00000000000..1a166119d16
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/javascript/XMLHttpRequest.java
@@ -0,0 +1,77 @@
+package org.keycloak.testsuite.adapter.javascript;
+
+import org.openqa.selenium.JavascriptExecutor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author mhajas
+ */
+public class XMLHttpRequest {
+
+ private String url;
+ private String method;
+ private Map headers;
+ private String content;
+
+ public static XMLHttpRequest create() {
+ return new XMLHttpRequest();
+ }
+
+ private XMLHttpRequest() {}
+
+ public XMLHttpRequest url(String url) {
+ this.url = url;
+ return this;
+ }
+
+ public XMLHttpRequest method(String method) {
+ this.method = method;
+ return this;
+ }
+
+ public XMLHttpRequest content(String content) {
+ this.content = content;
+ return this;
+ }
+
+ public XMLHttpRequest addHeader(String key, String value) {
+ if (headers == null) {
+ headers = new HashMap<>();
+ }
+
+ headers.put(key, value);
+
+ return this;
+ }
+
+ public Map send(JavascriptExecutor jsExecutor) {
+ String requestCode = "var callback = arguments[arguments.length - 1];" +
+ "var req = new XMLHttpRequest();" +
+ " req.open('" + method + "', '" + url + "', true);" +
+ getHeadersString() +
+ " req.onreadystatechange = function () {" +
+ " if (req.readyState == 4) {" +
+ " callback({\"status\" : req.status, \"reponseText\" : req.reponseText, \"responseHeaders\" : req.getAllResponseHeaders().toLowerCase(), \"res\" : req.response})" +
+ " }" +
+ " };" +
+ " req.send(" + content + ");";
+
+ return (Map) jsExecutor.executeAsyncScript(requestCode);
+ }
+
+ private String getHeadersString() {
+ StringBuilder builder = new StringBuilder();
+ for (Map.Entry entry : headers.entrySet()) {
+ builder.append("req.setRequestHeader('")
+ .append(entry.getKey())
+ .append("', '")
+ .append(entry.getValue())
+ .append("');");
+ }
+
+ return builder.toString();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
index 93ade25522e..d81d5cc9d11 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml
@@ -44,14 +44,23 @@
${chromeArguments}
-
+
5
5
10
5
-
+
+
+ ${js.browser}
+ ${htmlUnitBrowserVersion}
+ ${firefox_binary}
+ ${webdriver.chrome.driver}
+ ${js.chromeArguments}
+ ${phantomjs.cli.args} --ssl-certificates-path=${client.certificate.ca.path} --ssl-client-certificate-file=${client.certificate.file} --ssl-client-key-file=${client.key.file} --ssl-client-key-passphrase=${client.key.passphrase}
+
+
${browser}
${firefox_binary}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPJSConsoleExampleAdapterTest.java
deleted file mode 100644
index e9cabc4891c..00000000000
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap/src/test/java/org/keycloak/testsuite/adapter/example/EAPJSConsoleExampleAdapterTest.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.keycloak.testsuite.adapter.example;
-
-import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
-
-/**
- * @author tkyjovsk
- */
-@AppServerContainer("app-server-eap")
-public class EAPJSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
-
-}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6JSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6JSConsoleExampleAdapterTest.java
deleted file mode 100644
index c2911b53b55..00000000000
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/example/EAP6JSConsoleExampleAdapterTest.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.keycloak.testsuite.adapter.example;
-
-import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
-
-/**
- * @author tkyjovsk
- */
-@AppServerContainer("app-server-eap6")
-public class EAP6JSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
-
-}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPJSConsoleExampleAdapterTest.java
deleted file mode 100644
index 465ae456d2f..00000000000
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/eap/src/test/java/org/keycloak/testsuite/adapter/example/RelativeEAPJSConsoleExampleAdapterTest.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.keycloak.testsuite.adapter.example;
-
-/**
- *
- * @author tkyjovsk
- */
-public class RelativeEAPJSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
-
-}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/RelativeWildflyJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/RelativeWildflyJSConsoleExampleAdapterTest.java
deleted file mode 100644
index 7bf44bcca34..00000000000
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/relative/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/RelativeWildflyJSConsoleExampleAdapterTest.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.keycloak.testsuite.adapter.example;
-
-/**
- *
- * @author tkyjovsk
- */
-public class RelativeWildflyJSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
-
-}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteJSConsoleExampleAdapterTest.java
deleted file mode 100644
index 36d5e4a3aa8..00000000000
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/remote/src/test/java/org/keycloak/testsuite/adapter/example/RemoteJSConsoleExampleAdapterTest.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.keycloak.testsuite.adapter.example;
-
-import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
-
-/**
- * @author tkyjovsk
- */
-@AppServerContainer("app-server-remote")
-public class RemoteJSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
-
-}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyJSConsoleExampleAdapterTest.java
deleted file mode 100644
index bc9c7ae9498..00000000000
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/example/WildflyJSConsoleExampleAdapterTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-
-package org.keycloak.testsuite.adapter.example;
-
-import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
-
-/**
- *
- * @author tkyjovsk
- */
-@AppServerContainer("app-server-wildfly")
-public class WildflyJSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
-
-}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10JSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10JSConsoleExampleAdapterTest.java
deleted file mode 100644
index 565ca53259b..00000000000
--- a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly10/src/test/java/org/keycloak/testsuite/adapter/example/Wildfly10JSConsoleExampleAdapterTest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-
-package org.keycloak.testsuite.adapter.example;
-
-import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
-
-/**
- *
- * @author tkyjovsk
- */
-@AppServerContainer("app-server-wildfly10")
-public class Wildfly10JSConsoleExampleAdapterTest extends AbstractJSConsoleExampleAdapterTest {
-
-}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
index 2fc66a4402f..b7e973289f3 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml
@@ -267,18 +267,6 @@
-
- org.keycloak.testsuite
- integration-arquillian-test-apps-js-console
- ${project.version}
- war
-
-
- org.keycloak.testsuite
- integration-arquillian-test-apps-js-database
- ${project.version}
- war
-
org.keycloak.testsuite
hello-world-authz-service
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index 2af22bea8d4..1e296445cf5 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -103,6 +103,8 @@
+ phantomjs
+ --headless
chrome
--ignore-ssl-errors=true --web-security=false --ssl-certificates-path=${client.certificate.ca.path} --ssl-client-certificate-file=${client.certificate.file} --ssl-client-key-file=${client.key.file} --ssl-client-key-passphrase=${client.key.passphrase}
/usr/bin/firefox
@@ -268,6 +270,8 @@
${test.intermittent}
${browser}
+ ${js.browser}
+ ${js.chromeArguments}
${htmlUnitBrowserVersion}
${webdriverDownloadBinaries}