diff --git a/common/src/main/java/org/keycloak/common/util/HttpPostRedirect.java b/common/src/main/java/org/keycloak/common/util/HttpPostRedirect.java new file mode 100644 index 00000000000..0a421b8a7f8 --- /dev/null +++ b/common/src/main/java/org/keycloak/common/util/HttpPostRedirect.java @@ -0,0 +1,89 @@ +/* + * 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.common.util; + +import java.util.Map; + + +/** + * Helper class to do a browser redirect via a POST. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class HttpPostRedirect { + + /** + * Generate an HTML page that does a browser redirect via a POST. The HTML document uses Javascript to automatically + * submit a FORM post when loaded. + * + * This is similar to what the SAML Post Binding does. + * + * Here's an example + * + *
+     * {@code
+     * 
+     *   
+     *       title
+     *   
+     *   
+     *       
+ * + * + *
+ * + * + * } + *
+ + * + * @param title may be null. Just the title of the HTML document + * @param actionUrl URL to redirect to + * @param params must be encoded so that they can be placed in an HTML form hidden INPUT field value + * @return + */ + public String buildHtml(String title, String actionUrl, Map params) { + StringBuilder builder = new StringBuilder(); + builder.append("") + .append(""); + if (title != null) { + builder.append("SAML HTTP Post Binding"); + } + builder.append("") + .append("") + + .append("
"); + for (Map.Entry param : params.entrySet()) { + builder.append(""); + } + + + builder.append("") + + .append(""); + + return builder.toString(); + } + +} diff --git a/server-spi-private/src/main/java/org/keycloak/events/Errors.java b/server-spi-private/src/main/java/org/keycloak/events/Errors.java index ea2b8877e4e..e17743e19bb 100755 --- a/server-spi-private/src/main/java/org/keycloak/events/Errors.java +++ b/server-spi-private/src/main/java/org/keycloak/events/Errors.java @@ -75,4 +75,7 @@ public interface Errors { String PASSWORD_CONFIRM_ERROR = "password_confirm_error"; String PASSWORD_MISSING = "password_missing"; String PASSWORD_REJECTED = "password_rejected"; + String NOT_LOGGED_IN = "not_logged_in"; + String UNKNOWN_IDENTITY_PROVIDER = "unknown_identity_provider"; + String ILLEGAL_ORIGIN = "illegal_origin"; } diff --git a/server-spi-private/src/main/java/org/keycloak/events/EventType.java b/server-spi-private/src/main/java/org/keycloak/events/EventType.java index f97b713b28e..f77137fc0a5 100755 --- a/server-spi-private/src/main/java/org/keycloak/events/EventType.java +++ b/server-spi-private/src/main/java/org/keycloak/events/EventType.java @@ -113,7 +113,10 @@ public enum EventType { CLIENT_UPDATE(true), CLIENT_UPDATE_ERROR(true), CLIENT_DELETE(true), - CLIENT_DELETE_ERROR(true); + CLIENT_DELETE_ERROR(true), + + CLIENT_INITIATED_ACCOUNT_LINKING(true), + CLIENT_INITIATED_ACCOUNT_LINKING_ERROR(true); private boolean saveByDefault; diff --git a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java index 170925b63d4..eb0dd696ce3 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java @@ -216,8 +216,10 @@ public abstract class AbstractOAuth2IdentityProvider resourceAccess = token.getResourceAccess(); + AccessToken.Access brokerRoles = resourceAccess == null ? null : resourceAccess.get(Constants.BROKER_SERVICE_CLIENT_ID); + return brokerRoles != null && brokerRoles.isUserInRole(Constants.READ_TOKEN_ROLE); + } + private Response getToken(String providerId, boolean forceRetrieval) { this.event.event(EventType.IDENTITY_PROVIDER_RETRIEVE_TOKEN); @@ -226,9 +400,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return corsResponse(forbidden("Realm has not migrated to support the broker token exchange service"), clientModel); } - Map resourceAccess = token.getResourceAccess(); - AccessToken.Access brokerRoles = resourceAccess == null ? null : resourceAccess.get(Constants.BROKER_SERVICE_CLIENT_ID); - if (brokerRoles == null || !brokerRoles.isUserInRole(Constants.READ_TOKEN_ROLE)) { + if (!canReadBrokerToken(token)) { return corsResponse(forbidden("Client [" + clientModel.getClientId() + "] not authorized to retrieve tokens from identity provider [" + providerId + "]."), clientModel); } @@ -366,6 +538,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal // Callback from LoginActionsService after first login with broker was done and Keycloak account is successfully linked/created @GET + @NoCache @Path("/after-first-broker-login") public Response afterFirstBrokerLogin(@QueryParam("code") String code) { ParsedCodeContext parsedCode = parseClientSessionCode(code); @@ -487,6 +660,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal // Callback from LoginActionsService after postBrokerLogin flow is finished @GET + @NoCache @Path("/after-post-broker-login") public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) { ParsedCodeContext parsedCode = parseClientSessionCode(code); @@ -613,7 +787,19 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal this.event.event(EventType.FEDERATED_IDENTITY_LINK); if (federatedUser != null) { - return redirectToAccountErrorPage(clientSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias()); + // refresh the token + if (context.getIdpConfig().isStoreToken()) { + federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel); + if (!ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) { + + this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel); + + if (isDebugEnabled()) { + logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias()); + } + } + } + return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build(); } UserModel authenticatedUser = clientSession.getUserSession().getUser(); @@ -645,15 +831,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal FederatedIdentityModel federatedIdentityModel = this.session.users().getFederatedIdentity(federatedUser, context.getIdpConfig().getAlias(), this.realmModel); // Skip DB write if tokens are null or equal - if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) { - federatedIdentityModel.setToken(context.getToken()); - - this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel); - - if (isDebugEnabled()) { - logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias()); - } - } + updateToken(context, federatedUser, federatedIdentityModel); context.getIdp().updateBrokeredUser(session, realmModel, federatedUser, context); Set mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias()); if (mappers != null) { @@ -666,6 +844,18 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } + private void updateToken(BrokeredIdentityContext context, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { + if (context.getIdpConfig().isStoreToken() && !ObjectUtil.isEqualOrBothNull(context.getToken(), federatedIdentityModel.getToken())) { + federatedIdentityModel.setToken(context.getToken()); + + this.session.users().updateFederatedIdentity(this.realmModel, federatedUser, federatedIdentityModel); + + if (isDebugEnabled()) { + logger.debugf("Identity [%s] update with response from identity provider [%s].", federatedUser, context.getIdpConfig().getAlias()); + } + } + } + private ParsedCodeContext parseClientSessionCode(String code) { ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel); diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ClientInitiatedAccountLinkServlet.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ClientInitiatedAccountLinkServlet.java new file mode 100644 index 00000000000..43095662c34 --- /dev/null +++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ClientInitiatedAccountLinkServlet.java @@ -0,0 +1,97 @@ +/* + * 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.servlet; + +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.common.util.Base64Url; +import org.keycloak.common.util.KeycloakUriBuilder; +import org.keycloak.representations.AccessToken; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.UUID; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ClientInitiatedAccountLinkServlet extends HttpServlet { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse resp) throws ServletException, IOException { + if (request.getRequestURI().endsWith("/link") && request.getParameter("response") == null) { + String provider = request.getParameter("provider"); + String realm = request.getParameter("realm"); + KeycloakSecurityContext session = (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName()); + AccessToken token = session.getToken(); + String clientSessionId = token.getClientSession(); + String nonce = UUID.randomUUID().toString(); + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + String input = nonce + token.getSessionState() + clientSessionId + provider; + byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8)); + String hash = Base64Url.encode(check); + request.getSession().setAttribute("hash", hash); + String redirectUri = KeycloakUriBuilder.fromUri(request.getRequestURL().toString()) + .replaceQuery(null) + .queryParam("response", "true").build().toString(); + String accountLinkUrl = KeycloakUriBuilder.fromUri(ServletTestUtils.getAuthServerUrlBase()) + .path("/auth/realms/{realm}/broker/{provider}/link") + .queryParam("nonce", nonce) + .queryParam("hash", hash) + .queryParam("client_id", token.getIssuedFor()) + .queryParam("redirect_uri", redirectUri).build(realm, provider).toString(); + resp.setStatus(302); + resp.setHeader("Location", accountLinkUrl); + } else if (request.getRequestURI().endsWith("/link") && request.getParameter("response") != null) { + String hash = request.getSession().getAttribute("hash").toString(); + String hashParam = request.getParameter("hash"); + resp.setStatus(200); + resp.setContentType("text/html"); + PrintWriter pw = resp.getWriter(); + pw.printf("%s", "Client Linking"); + String error = request.getParameter("error"); + if (error != null) { + pw.println("Link error: " + error); + } else { + pw.println("Account Linked"); + } + pw.print(""); + pw.flush(); + } else { + resp.setStatus(200); + resp.setContentType("text/html"); + PrintWriter pw = resp.getWriter(); + pw.printf("%s", "Client Linking"); + pw.println("Unknown request: " + request.getRequestURL().toString()); + pw.print(""); + pw.flush(); + + } + + } +} diff --git a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ServletTestUtils.java b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ServletTestUtils.java index 8097c4f3239..8a064a66eec 100644 --- a/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ServletTestUtils.java +++ b/testsuite/integration-arquillian/test-apps/servlets/src/main/java/org/keycloak/testsuite/adapter/servlet/ServletTestUtils.java @@ -51,6 +51,6 @@ public class ServletTestUtils { return System.getProperty("auth.server.ssl.base.url", "https://localhost:8543"); } - return System.getProperty("auth.server.base.url"); + return System.getProperty("auth.server.base.url", "http://localhost:8180"); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java index 94a8fb6b973..11d8fb2f7bf 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/pages/LoginPage.java @@ -144,7 +144,12 @@ public class LoginPage extends AbstractPage { public boolean isCurrent() { - return driver.getTitle().equals("Log in to test") || driver.getTitle().equals("Anmeldung bei test"); + String realm = "test"; + return isCurrent(realm); + } + + public boolean isCurrent(String realm) { + return driver.getTitle().equals("Log in to " + realm) || driver.getTitle().equals("Anmeldung bei " + realm); } public void clickRegister() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/ClientInitiatedAccountLinkTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/ClientInitiatedAccountLinkTest.java new file mode 100644 index 00000000000..6c08851c057 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/ClientInitiatedAccountLinkTest.java @@ -0,0 +1,245 @@ +/* + * 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.broker; + +import org.apache.http.client.utils.URIBuilder; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.graphene.page.Page; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.common.util.Base64Url; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.FederatedIdentityRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.adapter.page.AppServerContextRoot; +import org.keycloak.testsuite.adapter.servlet.ClientInitiatedAccountLinkServlet; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; +import org.keycloak.testsuite.federation.PassThroughFederatedUserStorageProvider; +import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; +import org.keycloak.testsuite.pages.AccountFederatedIdentityPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.UpdateAccountInformationPage; +import org.keycloak.testsuite.util.AdapterServletDeployment; + +import javax.ws.rs.core.UriBuilder; +import java.net.URL; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; + +import static org.junit.Assert.assertTrue; +import static org.keycloak.testsuite.admin.ApiUtil.createUserAndResetPasswordWithAdminClient; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@AppServerContainer("auth-server-undertow") +public class ClientInitiatedAccountLinkTest extends AbstractKeycloakTest { + public static final String CHILD_IDP = "child"; + public static final String PARENT_IDP = "parent-idp"; + public static final String PARENT_USERNAME = "parent"; + + @Page + protected UpdateAccountInformationPage profilePage; + + @Page + protected LoginPage loginPage; + + @Page + protected AppServerContextRoot appServerContextRootPage; + + public boolean isRelative() { + return testContext.isRelativeAdapterTest(); + } + + public static class ClientApp extends AbstractPageWithInjectedUrl { + + public static final String DEPLOYMENT_NAME = "client-linking"; + + @ArquillianResource + @OperateOnDeployment(DEPLOYMENT_NAME) + private URL url; + + @Override + public URL getInjectedUrl() { + return url; + } + + } + + @Page + protected ClientApp appPage; + + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = new RealmRepresentation(); + realm.setRealm(CHILD_IDP); + realm.setEnabled(true); + ClientRepresentation servlet = new ClientRepresentation(); + servlet.setClientId("client-linking"); + servlet.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL); + String uri = "/client-linking"; + if (!isRelative()) { + uri = appServerContextRootPage.toString() + uri; + } + servlet.setAdminUrl(uri); + servlet.setBaseUrl(uri); + servlet.setRedirectUris(new LinkedList<>()); + servlet.getRedirectUris().add(uri + "/*"); + servlet.setSecret("password"); + servlet.setFullScopeAllowed(true); + realm.setClients(new LinkedList<>()); + realm.getClients().add(servlet); + testRealms.add(realm); + + + realm = new RealmRepresentation(); + realm.setRealm(PARENT_IDP); + realm.setEnabled(true); + + testRealms.add(realm); + + } + + @Deployment(name = "client-linking") + public static WebArchive customerPortal() { + return AdapterServletDeployment.oidcDeployment("client-linking", "/account-link-test", ClientInitiatedAccountLinkServlet.class); + } + + + @Before + public void addIdpUser() { + RealmResource realm = adminClient.realms().realm(PARENT_IDP); + UserRepresentation user = new UserRepresentation(); + user.setUsername(PARENT_USERNAME); + user.setEnabled(true); + String userId = createUserAndResetPasswordWithAdminClient(realm, user, "password"); + + } + + private String childUserId = null; + + @Before + public void addChildUser() { + RealmResource realm = adminClient.realms().realm(CHILD_IDP); + UserRepresentation user = new UserRepresentation(); + user.setUsername("child"); + user.setEnabled(true); + childUserId = createUserAndResetPasswordWithAdminClient(realm, user, "password"); + + // have to add a role as stupid undertow auth manager doesn't like "*" + realm.roles().create(new RoleRepresentation("user", null, false)); + RoleRepresentation role = realm.roles().get("user").toRepresentation(); + List roles = new LinkedList<>(); + roles.add(role); + realm.users().get(childUserId).roles().realmLevel().add(roles); + + } + + @Before + public void createBroker() { + createParentChild(); + } + + public void createParentChild() { + BrokerTestTools.createKcOidcBroker(adminClient, CHILD_IDP, PARENT_IDP, suiteContext); + } + + @Test + public void testErrorConditions() { + RealmResource realm = adminClient.realms().realm(CHILD_IDP); + List links = realm.users().get(childUserId).getFederatedIdentity(); + Assert.assertTrue(links.isEmpty()); + + UriBuilder redirectUri = UriBuilder.fromUri(appPage.getInjectedUrl().toString()) + .path("link") + .queryParam("response", "true"); + + UriBuilder directLinking = UriBuilder.fromUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth") + .path("realms/child/broker/{provider}/link") + .queryParam("client_id", "client-linking") + .queryParam("redirect_uri", redirectUri.build()) + .queryParam("hash", Base64Url.encode("crap".getBytes())) + .queryParam("nonce", UUID.randomUUID().toString()); + + String linkUrl = directLinking + .build(PARENT_IDP).toString(); + // test not logged in + + + driver.navigate().to(linkUrl); + + Assert.assertTrue(driver.getCurrentUrl().contains("error=not_logged_in")); + + + // now log in + + driver.navigate().to( appPage.getInjectedUrl() + "/hello"); + loginPage.login("child", "password"); + Assert.assertTrue(driver.getCurrentUrl().startsWith(appPage.getInjectedUrl() + "/hello")); + + // now test CSRF with bad hash. + + driver.navigate().to(linkUrl); + + Assert.assertTrue(driver.getPageSource().contains("We're sorry...")); + + + + } + + @Test + public void testAccountLink() { + RealmResource realm = adminClient.realms().realm(CHILD_IDP); + List links = realm.users().get(childUserId).getFederatedIdentity(); + Assert.assertTrue(links.isEmpty()); + + UriBuilder linkBuilder = UriBuilder.fromUri(appPage.getInjectedUrl().toString()) + .path("link"); + String linkUrl = linkBuilder.clone() + .queryParam("realm", CHILD_IDP) + .queryParam("provider", PARENT_IDP).build().toString(); + driver.navigate().to(linkUrl); + Assert.assertTrue(loginPage.isCurrent(CHILD_IDP)); + loginPage.login("child", "password"); + Assert.assertTrue(loginPage.isCurrent(PARENT_IDP)); + loginPage.login(PARENT_USERNAME, "password"); + System.out.println("After linking: " + driver.getCurrentUrl()); + System.out.println(driver.getPageSource()); + Assert.assertTrue(driver.getCurrentUrl().startsWith(linkBuilder.toTemplate())); + Assert.assertTrue(driver.getPageSource().contains("Account Linked")); + + links = realm.users().get(childUserId).getFederatedIdentity(); + Assert.assertFalse(links.isEmpty()); + } + + + + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdapterServletDeployment.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdapterServletDeployment.java new file mode 100644 index 00000000000..d8328c262f0 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/AdapterServletDeployment.java @@ -0,0 +1,93 @@ +/* + * 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.util; + +import org.apache.commons.io.IOUtils; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; + +import java.io.IOException; +import java.net.URL; + +/** + * Expects a structure like adapter-test directory + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class AdapterServletDeployment { + + public static final String JBOSS_DEPLOYMENT_STRUCTURE_XML = "jboss-deployment-structure.xml"; + public static final String TOMCAT_CONTEXT_XML = "context.xml"; + + // hardcoded for now + public static final URL tomcatContext = AdapterServletDeployment.class + .getResource("/adapter-test/" + TOMCAT_CONTEXT_XML); + + public static WebArchive oidcDeployment(String name, String configRoot, Class... servletClasses) { + return oidcDeployment(name, configRoot, "keycloak.json"); + + } + + + public static WebArchive oidcDeployment(String name, String configRoot, String adapterConfigFilename, Class... servletClasses) { + String configPath = configRoot + "/" + name; + String webInfPath = configPath + "/WEB-INF/"; + + URL keycloakJSON = AdapterServletDeployment.class.getResource(webInfPath + adapterConfigFilename); + URL webXML = AdapterServletDeployment.class.getResource(webInfPath + "web.xml"); + + WebArchive deployment = ShrinkWrap.create(WebArchive.class, name + ".war") + .addClasses(servletClasses) + .addAsWebInfResource(webXML, "web.xml"); + + URL keystore = AdapterServletDeployment.class.getResource(webInfPath + "keystore.jks"); + if (keystore != null) { + deployment.addAsWebInfResource(keystore, "classes/keystore.jks"); + } + + if (keycloakJSON != null) { + deployment.addAsWebInfResource(keycloakJSON, "keycloak.json"); + } + + URL jbossDeploymentStructure = AdapterServletDeployment.class.getResource(webInfPath + JBOSS_DEPLOYMENT_STRUCTURE_XML); + if (jbossDeploymentStructure == null) { + jbossDeploymentStructure = AdapterServletDeployment.class.getResource(configRoot + "/" + JBOSS_DEPLOYMENT_STRUCTURE_XML); + } + if (jbossDeploymentStructure != null) deployment.addAsWebInfResource(jbossDeploymentStructure, JBOSS_DEPLOYMENT_STRUCTURE_XML); + + addContextXml(deployment, name); + + return deployment; + } + + + public static void addContextXml(Archive archive, String contextPath) { + // hardcoded for now + try { + String contextXmlContent = IOUtils.toString(tomcatContext.openStream()) + .replace("%CONTEXT_PATH%", contextPath); + archive.add(new StringAsset(contextXmlContent), "/META-INF/context.xml"); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/childrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/childrealm.json new file mode 100644 index 00000000000..57cc7e8e74b --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/childrealm.json @@ -0,0 +1,38 @@ +{ + "id": "child", + "realm": "child", + "enabled": true, + "accessTokenLifespan": 600, + "accessCodeLifespan": 10, + "accessCodeLifespanUserAction": 6000, + "sslRequired": "external", + "registrationAllowed": false, + "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" : "bburke@redhat.com", + "enabled": true, + "email" : "bburke@redhat.com", + "firstName": "Bill", + "lastName": "Burke", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ] + } + ], + "clients": [ + { + "clientId": "client-linking", + "enabled": true, + "adminUrl": "/client-linking", + "baseUrl": "/client-linking", + "redirectUris": [ + "/client-linking/*" + ], + "secret": "password" + } + ] +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/META-INF/context.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/META-INF/context.xml new file mode 100644 index 00000000000..b4ddcce3866 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/META-INF/context.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/jetty-web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/jetty-web.xml new file mode 100644 index 00000000000..8c59313878f --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/jetty-web.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/keycloak.json new file mode 100644 index 00000000000..c447d58ed00 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/keycloak.json @@ -0,0 +1,9 @@ +{ + "realm" : "child", + "resource" : "client-linking", + "auth-server-url" : "http://localhost:8180/auth", + "ssl-required" : "external", + "credentials" : { + "secret": "password" + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/web.xml new file mode 100644 index 00000000000..81425886b6c --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/account-link-test/client-linking/WEB-INF/web.xml @@ -0,0 +1,54 @@ + + + + + + client-linking + + + Servlet + org.keycloak.testsuite.adapter.servlet.ClientInitiatedAccountLinkServlet + + + + Servlet + /* + + + + + Users + /* + + + user + + + + + KEYCLOAK + child + + + + user + + diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java index 6e3da8d45c1..4a2ce9693d3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTest.java @@ -104,21 +104,6 @@ public class AdapterTest { Thread.sleep(1000000000); } - public static class MySuper { - - } - - public static class Base extends MySuper { - public Class superClass() { - return super.getClass(); - } - } - - @Test - public void testBase() { - System.out.println(new Base().superClass().getName()); - } - @Test public void testLoginSSOAndLogout() throws Exception { testStrategy.testLoginSSOMax(); diff --git a/testsuite/pom.xml b/testsuite/pom.xml index ea6253f5bf5..1edd1861050 100755 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -57,6 +57,7 @@ tomcat8 jetty integration-arquillian + stress