diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css index 2e0159bbed..5f105e2d27 100644 --- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css +++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.css @@ -453,7 +453,6 @@ input[type="email"].tiny { background-image: url(img/chosen-arrow-down.png), -moz-linear-gradient(center top, #eeeeee 0%, #ffffff 50%); border-bottom: none; border-radius: 2px 2px 0 0; - background-image: ; background-repeat: no-repeat; background-position: right top; } @@ -524,7 +523,7 @@ input[type="email"].tiny { line-height: 1.45454545454545em; } .tokenfield.form-control .token .close { - text-indent: -9999999em; + text-indent: -99999em; width: 1.6em; height: 1.6em; line-height: 1.6em; @@ -569,7 +568,7 @@ input[type="email"].tiny { line-height: 1.45454545454545em; } .token .close { - text-indent: -9999999em; + text-indent: -99999em; width: 1.6em; height: 1.6em; line-height: 1.6em; diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less index 4b0321edb8..dfc353d9e5 100644 --- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less +++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/forms.less @@ -544,7 +544,6 @@ input[type="email"] { background-image: url(img/chosen-arrow-down.png), -moz-linear-gradient(center top , #eee 0%, #fff 50%); border-bottom: none; border-radius: 2px 2px 0 0; - background-image: ; background-repeat: no-repeat; background-position: right top; } @@ -628,7 +627,7 @@ input[type="email"] { } .close { - text-indent: -9999999em; + text-indent: -99999em; width: 1.6em; height: 1.6em; line-height: 1.6em; @@ -680,7 +679,7 @@ input[type="email"] { } .close { - text-indent: -9999999em; + text-indent: -99999em; width: 1.6em; height: 1.6em; line-height: 1.6em; diff --git a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/sprites.css b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/sprites.css index fcfb0cc190..1a35f748bd 100755 --- a/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/sprites.css +++ b/admin-ui-styles/src/main/resources/META-INF/resources/admin-ui/css/sprites.css @@ -5,7 +5,7 @@ height: 16px; background-image: url(img/sprites.png); /* Modified by Gabriel */ background-repeat: no-repeat; - text-indent: -9999999em; + text-indent: -99999em; margin-right: 0.5em; vertical-align: text-top; } diff --git a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-detail.html b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-detail.html index 087e45b6a1..84aa0c6392 100755 --- a/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-detail.html +++ b/examples/as7-eap-demo/server/src/main/webapp/saas/admin/partials/realm-detail.html @@ -60,8 +60,8 @@
- -
- <#if error?has_content> + <#if !isErrorPage && error?has_content>

${rb.getString(error.summary)} diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 8ca1511561..b78d9edc4e 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -296,6 +296,7 @@ public class AccountService { return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode).setUser(user) .forwardToAction(requiredActions.iterator().next()); } else { + accessCode.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan()); return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode, accessCode.getState(), accessCode.getRedirectUri()); } diff --git a/services/src/main/java/org/keycloak/services/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java index c13eb25eb1..57a5aafc8b 100755 --- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java +++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java @@ -57,7 +57,6 @@ import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.TokenManager; import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.OAuthFlows; -import org.keycloak.services.resources.flows.PageFlows; import org.keycloak.services.resources.flows.Urls; import org.keycloak.social.AuthCallback; import org.keycloak.social.AuthRequest; @@ -221,9 +220,12 @@ public class SocialResource { @QueryParam("provider_id") final String providerId, @QueryParam("client_id") final String clientId, @QueryParam("scope") final String scope, @QueryParam("state") final String state, @QueryParam("redirect_uri") final String redirectUri) { + RealmManager realmManager = new RealmManager(session); + RealmModel realm = realmManager.getRealm(realmId); + SocialProvider provider = getProvider(providerId); if (provider == null) { - return Flows.pages(request).forwardToSecurityFailure("Social provider not found"); + return Flows.forms(realm, request, uriInfo).setError("Social provider not found").forwardToErrorPage(); } String key = System.getProperty("keycloak.social." + providerId + ".key"); @@ -244,7 +246,7 @@ public class SocialResource { return Response.status(Status.FOUND).location(authRequest.getAuthUri()).build(); } catch (Throwable t) { - return Flows.pages(request).forwardToSecurityFailure("Failed to redirect to social auth"); + return Flows.forms(realm, request, uriInfo).setError("Failed to redirect to social auth").forwardToErrorPage(); } } @@ -253,24 +255,24 @@ public class SocialResource { @Consumes(MediaType.APPLICATION_FORM_URLENCODED) public Response socialRegistration(@PathParam("realm") final String realmId, final MultivaluedMap formData) { - PageFlows pageFlows = Flows.pages(request); + RealmManager realmManager = new RealmManager(session); + RealmModel realm = realmManager.getRealm(realmId); + Cookie cookie = headers.getCookies().get(SocialConstants.SOCIAL_REGISTRATION_COOKIE); if (cookie == null) { - return pageFlows.forwardToSecurityFailure("Social registration cookie not found"); + return Flows.forms(realm, request, uriInfo).setError("Social registration cookie not found").forwardToErrorPage(); } String requestId = cookie.getValue(); if (!socialRequestManager.isRequestId(requestId)) { logger.error("Unknown requestId found in cookie. Maybe it's expired. requestId=" + requestId); - return pageFlows.forwardToSecurityFailure("Unknown requestId found in cookie. Maybe it's expired."); + return Flows.forms(realm, request, uriInfo).setError("Unknown requestId found in cookie. Maybe it's expired.").forwardToErrorPage(); } RequestDetails requestData = socialRequestManager.getData(requestId); - RealmManager realmManager = new RealmManager(session); - RealmModel realm = realmManager.getRealm(realmId); if (realm == null || !realm.isEnabled()) { - return pageFlows.forwardToSecurityFailure("Realm doesn't exists or is not enabled."); + return Flows.forms(realm, request, uriInfo).setError("Realm doesn't exists or is not enabled.").forwardToErrorPage(); } TokenService tokenService = new TokenService(realm, tokenManager); resourceContext.initResource(tokenService); diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java index c16896204b..ae4c014bbf 100755 --- a/services/src/main/java/org/keycloak/services/resources/TokenService.java +++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java @@ -544,6 +544,7 @@ public class TokenService { return redirectAccessDenied(redirect, state); } + accessCodeEntry.setExpiration((System.currentTimeMillis() / 1000) + realm.getAccessCodeLifespan()); return oauth.redirectAccessCode(accessCodeEntry, state, redirect); } diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java index ddb1afe731..c8711ea370 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java @@ -36,10 +36,6 @@ public class Flows { private Flows() { } - public static PageFlows pages(HttpRequest request) { - return new PageFlows(request); - } - public static FormFlows forms(RealmModel realm, HttpRequest request, UriInfo uriInfo) { return new FormFlows(realm, request, uriInfo); } diff --git a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java index d45588a3bf..cdb588e2e5 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/FormFlows.java @@ -168,6 +168,10 @@ public class FormFlows { return forwardToForm(Pages.TOTP); } + public Response forwardToErrorPage() { + return forwardToForm(Pages.ERROR); + } + public FormFlows setAccessCode(AccessCodeEntry accessCode) { this.accessCode = accessCode; return this; diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java index 16cc933e00..5981dcba22 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java @@ -126,7 +126,7 @@ public class OAuthFlows { } public Response forwardToSecurityFailure(String message) { - return Flows.pages(request).forwardToSecurityFailure(message); + return Flows.forms(realm, request, uriInfo).setError(message).forwardToErrorPage(); } } diff --git a/services/src/main/java/org/keycloak/services/resources/flows/PageFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/PageFlows.java deleted file mode 100755 index dd6edb812d..0000000000 --- a/services/src/main/java/org/keycloak/services/resources/flows/PageFlows.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * JBoss, Home of Professional Open Source. - * Copyright 2012, Red Hat, Inc., and individual contributors - * as indicated by the @author tags. See the copyright.txt file in the - * distribution for a full listing of individual contributors. - * - * This is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation; either version 2.1 of - * the License, or (at your option) any later version. - * - * This software is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - */ -package org.keycloak.services.resources.flows; - -import org.jboss.resteasy.logging.Logger; -import org.jboss.resteasy.spi.HttpRequest; -import org.keycloak.services.JspRequestParameters; - -import javax.ws.rs.core.Response; - -/** - * @author Stian Thorgersen - */ -public class PageFlows { - - private static final Logger log = Logger.getLogger(PageFlows.class); - - private HttpRequest request; - - PageFlows(HttpRequest request) { - this.request = request; - } - - public Response forwardToSecurityFailure(String message) { - log.error(message); - - request.setAttribute(JspRequestParameters.KEYCLOAK_SECURITY_FAILURE_MESSAGE, message); - - request.forward(Pages.SECURITY_FAILURE); - return null; - } - -} diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Pages.java b/services/src/main/java/org/keycloak/services/resources/flows/Pages.java index a5f913d380..110da3ad25 100644 --- a/services/src/main/java/org/keycloak/services/resources/flows/Pages.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Pages.java @@ -48,7 +48,7 @@ public class Pages { public final static String REGISTER = "/forms/register.ftl"; - public final static String SECURITY_FAILURE = "/saas/securityFailure.jsp"; + public final static String ERROR = "/forms/error.ftl"; public final static String SOCIAL = "/forms/social.ftl"; diff --git a/services/src/test/java/TotpUtil.java b/services/src/test/java/TotpUtil.java deleted file mode 100644 index 31a5296b9e..0000000000 --- a/services/src/test/java/TotpUtil.java +++ /dev/null @@ -1,14 +0,0 @@ -import org.picketlink.common.util.Base32; -import org.picketlink.idm.credential.util.TimeBasedOTP; - -public class TotpUtil { - - public static void main(String[] args) { - String google = "PJBX GURY NZIT C2JX I44T S3D2 JBKD G6SB"; - google = google.replace(" ", ""); - google = new String(Base32.decode(google)); - TimeBasedOTP otp = new TimeBasedOTP(); - System.out.println(otp.generate(google)); - } - -} diff --git a/testsuite/README.md b/testsuite/README.md index 823035db89..00d5c0c85c 100644 --- a/testsuite/README.md +++ b/testsuite/README.md @@ -6,4 +6,49 @@ Browser The testsuite uses Sellenium. By default it uses the HtmlUnit WebDriver, but can also be executed with Chrome or Firefox. -To run the tests with Firefox add `-Dbrowser=firefox` or for Chrome add `-Dbrowser=chrome` \ No newline at end of file +To run the tests with Firefox add `-Dbrowser=firefox` or for Chrome add `-Dbrowser=chrome` + + +Test utils +========== + +Keycloak server +--------------- + +To start a basic Keycloak server for testing run: + + mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer + +or just run KeycloakServer from your favourite IDE! + +When starting the server it can also import a realm from a json file: + + mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer -Dexec.args="-import testrealm.json" + +You can also change the host and port the server is bound to: + + mvn exec:java -Dexec.mainClass=org.keycloak.testutils.KeycloakServer -Dexec.args="-b host -p 8080" + +TOTP codes +---------- + +To generate totp codes without Google authenticator run: + + mvn exec:java -Dexec.mainClass=org.keycloak.testutils.TotpGenerator -Dexec.args="PJBX GURY NZIT C2JX I44T S3D2 JBKD G6SB" + +or just run TotpGenerator from your favourite IDE! + +Replace value of -Dexec.args with the secret from the totp configuration page + +Mail server +----------- + +To start a test mail server for testing email sending run: + + mvn exec:java -Dexec.mainClass=org.keycloak.testutils.MailServer + +or just run MailServer from your favourite IDE! + +To configure Keycloak to use the above server add: + + -Dkeycloak.mail.smtp.from=auto@keycloak.org -Dkeycloak.mail.smtp.host=localhost -Dkeycloak.mail.smtp.port=3025 diff --git a/testsuite/pom.xml b/testsuite/pom.xml index cd9c0b52b2..329ddaea52 100755 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -26,7 +26,6 @@ org.bouncycastle bcprov-jdk16 - provided org.keycloak @@ -38,6 +37,11 @@ keycloak-services ${project.version} + + org.keycloak + keycloak-server + ${project.version} + org.keycloak keycloak-social-core @@ -67,37 +71,30 @@ org.jboss.logging jboss-logging - provided org.picketlink picketlink-idm-api - provided org.picketlink picketlink-common - provided org.picketlink picketlink-idm-impl - provided org.picketlink picketlink-idm-simple-schema - provided org.picketlink picketlink-config - provided org.jboss.resteasy resteasy-jaxrs - provided log4j @@ -116,89 +113,72 @@ org.jboss.resteasy jaxrs-api - provided org.jboss.resteasy resteasy-client - provided org.jboss.resteasy resteasy-crypto - provided org.jboss.resteasy jose-jwt - provided org.jboss.resteasy resteasy-undertow - test io.undertow undertow-servlet - test io.undertow undertow-core - test org.codehaus.jackson jackson-core-asl - provided org.codehaus.jackson jackson-mapper-asl - provided org.jboss.spec.javax.servlet jboss-servlet-api_3.0_spec - provided org.codehaus.jackson jackson-xc - provided junit junit - test org.hibernate.javax.persistence hibernate-jpa-2.0-api - provided com.h2database h2 1.3.161 - test org.hibernate hibernate-entitymanager 3.6.6.Final - test com.icegreen greenmail - test org.seleniumhq.selenium selenium-java - test diff --git a/testsuite/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/src/main/java/org/keycloak/testutils/KeycloakServer.java new file mode 100644 index 0000000000..e3d0afd8e7 --- /dev/null +++ b/testsuite/src/main/java/org/keycloak/testutils/KeycloakServer.java @@ -0,0 +1,264 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.testutils; + +import io.undertow.Undertow; +import io.undertow.Undertow.Builder; +import io.undertow.server.handlers.resource.ClassPathResourceManager; +import io.undertow.servlet.Servlets; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.FilterInfo; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.DispatcherType; + +import org.jboss.resteasy.jwt.JsonSerialization; +import org.jboss.resteasy.logging.Logger; +import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; +import org.jboss.resteasy.spi.ResteasyDeployment; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.FormService; +import org.keycloak.services.filters.KeycloakSessionServletFilter; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.services.resources.KeycloakApplication; +import org.keycloak.services.resources.SaasService; + +/** + * @author Stian Thorgersen + */ +public class KeycloakServer { + + private static final Logger log = Logger.getLogger(KeycloakServer.class); + + private boolean sysout = false; + + public static class KeycloakServerConfig { + private String host = "localhost"; + private int port = 8081; + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public void setHost(String host) { + this.host = host; + } + + public void setPort(int port) { + this.port = port; + } + } + + private static T loadJson(InputStream is, Class type) { + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + int c; + while ((c = is.read()) != -1) { + os.write(c); + } + byte[] bytes = os.toByteArray(); + return JsonSerialization.fromBytes(type, bytes); + } catch (IOException e) { + throw new RuntimeException("Failed to parse json", e); + } + } + + public static void main(String[] args) throws Throwable { + KeycloakServerConfig config = new KeycloakServerConfig(); + + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-b")) { + config.setHost(args[++i]); + } + + if (args[i].equals("-p")) { + config.setPort(Integer.valueOf(args[++i])); + } + } + + final KeycloakServer keycloak = new KeycloakServer(config); + keycloak.sysout = true; + keycloak.start(); + + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-import")) { + keycloak.importRealm(new FileInputStream(args[++i])); + } + } + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + keycloak.stop(); + } + }); + } + + private KeycloakServerConfig config; + + private KeycloakSessionFactory factory; + + private UndertowJaxrsServer server; + + public KeycloakServer() { + this(new KeycloakServerConfig()); + } + + public KeycloakServer(KeycloakServerConfig config) { + this.config = config; + } + + public KeycloakSessionFactory getKeycloakSessionFactory() { + return factory; + } + + public UndertowJaxrsServer getServer() { + return server; + } + + public void importRealm(InputStream realm) { + RealmRepresentation rep = loadJson(realm, RealmRepresentation.class); + importRealm(rep); + } + + public void importRealm(RealmRepresentation rep) { + KeycloakSession session = factory.createSession(); + session.getTransaction().begin(); + + try { + RealmManager manager = new RealmManager(session); + + if (rep.getId() == null) { + throw new RuntimeException("Realm id not specified"); + } + + if (manager.getRealm(rep.getId()) != null) { + info("Not importing realm " + rep.getRealm() + " realm already exists"); + return; + } + + RealmModel realm = manager.createRealm(rep.getId(), rep.getRealm()); + manager.importRealm(rep, realm); + + info("Imported realm " + realm.getName()); + + session.getTransaction().commit(); + } finally { + session.close(); + } + } + + protected void setupDefaultRealm() { + KeycloakSession session = factory.createSession(); + session.getTransaction().begin(); + + try { + RealmManager manager = new RealmManager(session); + + if (manager.getRealm(RealmModel.DEFAULT_REALM) != null) { + return; + } + + RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM); + manager.generateRealmKeys(defaultRealm); + + defaultRealm.setEnabled(true); + defaultRealm.setTokenLifespan(300); + defaultRealm.setAccessCodeLifespan(60); + defaultRealm.setAccessCodeLifespanUserAction(600); + defaultRealm.setSslNotRequired(false); + defaultRealm.setCookieLoginAllowed(true); + defaultRealm.setRegistrationAllowed(true); + defaultRealm.setAutomaticRegistrationAfterSocialLogin(false); + defaultRealm.setVerifyEmail(false); + + defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD); + RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE); + UserModel admin = defaultRealm.addUser("admin"); + defaultRealm.grantRole(admin, role); + + session.getTransaction().commit(); + } finally { + session.close(); + } + } + + public void start() throws Throwable { + long start = System.currentTimeMillis(); + + ResteasyDeployment deployment = new ResteasyDeployment(); + deployment.setApplicationClass(KeycloakApplication.class.getName()); + + Builder builder = Undertow.builder().addListener(config.getPort(), config.getHost()); + + server = new UndertowJaxrsServer().start(builder); + + DeploymentInfo di = server.undertowDeployment(deployment, "rest"); + di.setClassLoader(getClass().getClassLoader()); + di.setContextPath("/auth-server"); + di.setDeploymentName("Keycloak"); + di.setResourceManager(new ClassPathResourceManager(FormService.class.getClassLoader(), "META-INF/resources")); + + FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class); + di.addFilter(filter); + di.addFilterUrlMapping("SessionFilter", "/rest/*", DispatcherType.REQUEST); + + server.deploy(di); + + factory = KeycloakApplication.buildSessionFactory(); + + setupDefaultRealm(); + + info("Started Keycloak (http://" + config.getHost() + ":" + config.getPort() + "/auth-server) in " + + (System.currentTimeMillis() - start) + " ms\n"); + } + + private void info(String message) { + if (sysout) { + System.out.println(message); + } else { + log.info(message); + } + } + + public void stop() { + factory.close(); + server.stop(); + + info("Stopped Keycloak"); + } + +} diff --git a/testsuite/src/main/java/org/keycloak/testutils/MailServer.java b/testsuite/src/main/java/org/keycloak/testutils/MailServer.java new file mode 100644 index 0000000000..42302e3575 --- /dev/null +++ b/testsuite/src/main/java/org/keycloak/testutils/MailServer.java @@ -0,0 +1,30 @@ +package org.keycloak.testutils; + +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMessage.RecipientType; + +import com.icegreen.greenmail.util.GreenMail; +import com.icegreen.greenmail.util.ServerSetup; + +public class MailServer { + + public static void main(String[] args) throws Exception { + ServerSetup setup = new ServerSetup(3025, "localhost", "smtp"); + + GreenMail greenMail = new GreenMail(setup); + greenMail.start(); + + while (true) { + int c = greenMail.getReceivedMessages().length; + + if (greenMail.waitForIncomingEmail(Long.MAX_VALUE, c + 1)) { + MimeMessage message = greenMail.getReceivedMessages()[c++]; + System.out.println("Received mail to " + message.getRecipients(RecipientType.TO)[0]); + System.out.println(); + System.out.println(message.getContent()); + System.out.println("-------------------------------------------------------"); + } + } + } + +} diff --git a/testsuite/src/main/java/org/keycloak/testutils/TotpGenerator.java b/testsuite/src/main/java/org/keycloak/testutils/TotpGenerator.java new file mode 100644 index 0000000000..bc5f633b19 --- /dev/null +++ b/testsuite/src/main/java/org/keycloak/testutils/TotpGenerator.java @@ -0,0 +1,31 @@ +package org.keycloak.testutils; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; + +import org.picketlink.common.util.Base32; +import org.picketlink.idm.credential.util.TimeBasedOTP; + +public class TotpGenerator { + + public static void main(String[] args) { + String totp = ""; + for (String a : args) { + totp += a.trim(); + } + totp = totp.replace(" ", ""); + + final String google = new String(Base32.decode(totp)); + final TimeBasedOTP otp = new TimeBasedOTP(); + + Timer t = new Timer(); + t.schedule(new TimerTask() { + @Override + public void run() { + System.out.println(otp.generate(google)); + } + }, 0, TimeUnit.SECONDS.toMillis(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS)); + } + +} diff --git a/testsuite/src/test/resources/META-INF/persistence.xml b/testsuite/src/main/resources/META-INF/persistence.xml similarity index 100% rename from testsuite/src/test/resources/META-INF/persistence.xml rename to testsuite/src/main/resources/META-INF/persistence.xml diff --git a/testsuite/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/src/test/java/org/keycloak/testsuite/OAuthClient.java index 42d2c32394..dcda888985 100644 --- a/testsuite/src/test/java/org/keycloak/testsuite/OAuthClient.java +++ b/testsuite/src/test/java/org/keycloak/testsuite/OAuthClient.java @@ -24,6 +24,7 @@ package org.keycloak.testsuite; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.security.PublicKey; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -40,8 +41,11 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; +import org.jboss.resteasy.security.PemUtils; import org.json.JSONObject; import org.junit.Assert; +import org.keycloak.RSATokenVerifier; +import org.keycloak.representations.SkeletonKeyToken; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; @@ -52,7 +56,7 @@ public class OAuthClient { private WebDriver driver; - private String baseUrl = "http://localhost:8081/auth-server/rest"; + private String baseUrl = Constants.AUTH_SERVER_ROOT + "/rest"; private String realm = "test"; @@ -68,8 +72,13 @@ public class OAuthClient { private String state; - public OAuthClient(WebDriver driver) { + private PublicKey realmPublicKey; + + public OAuthClient(WebDriver driver) throws Exception { this.driver = driver; + + JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/testrealm.json"))); + realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey")); } public AuthorizationCodeResponse doLogin(String username, String password) { @@ -109,6 +118,10 @@ public class OAuthClient { return new AccessTokenResponse(client.execute(post)); } + public SkeletonKeyToken verifyToken(String token) throws Exception { + return RSATokenVerifier.verifyToken(token, realmPublicKey, realm); + } + public boolean isAuthorizationResponse() { return getCurrentRequest().equals(redirectUri) && getCurrentQuery().containsKey("code"); } diff --git a/testsuite/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index 16177675ce..15f1cbf9d2 100644 --- a/testsuite/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -21,17 +21,10 @@ */ package org.keycloak.testsuite.oauth; -import java.security.PublicKey; - -import org.apache.commons.io.IOUtils; -import org.jboss.resteasy.security.PemUtils; -import org.json.JSONObject; import org.junit.Assert; -import org.junit.Before; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; -import org.keycloak.RSATokenVerifier; import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.OAuthClient.AccessTokenResponse; @@ -61,14 +54,6 @@ public class AccessTokenTest { @WebResource protected LoginPage loginPage; - private PublicKey realmPublicKey; - - @Before - public void before() throws Exception { - JSONObject realmJson = new JSONObject(IOUtils.toString(getClass().getResourceAsStream("/testrealm.json"))); - realmPublicKey = PemUtils.decodePublicKey(realmJson.getString("publicKey")); - } - @Test public void accessTokenRequest() throws Exception { oauth.doLogin("test-user@localhost", "password"); @@ -82,7 +67,8 @@ public class AccessTokenTest { Assert.assertEquals("bearer", response.getTokenType()); - SkeletonKeyToken token = RSATokenVerifier.verifyToken(response.getAccessToken(), realmPublicKey, oauth.getRealm()); + SkeletonKeyToken token = oauth.verifyToken(response.getAccessToken()); + Assert.assertEquals("test-user@localhost", token.getPrincipal()); Assert.assertEquals(1, token.getRealmAccess().getRoles().size()); diff --git a/testsuite/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java index 2cc0df9905..7de8741e0c 100755 --- a/testsuite/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java +++ b/testsuite/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java @@ -21,81 +21,45 @@ */ package org.keycloak.testsuite.rule; -import io.undertow.server.handlers.resource.ClassPathResourceManager; -import io.undertow.servlet.Servlets; import io.undertow.servlet.api.DeploymentInfo; -import io.undertow.servlet.api.FilterInfo; import io.undertow.servlet.api.ServletInfo; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import javax.servlet.DispatcherType; import javax.servlet.Servlet; import org.jboss.resteasy.jwt.JsonSerialization; -import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer; -import org.jboss.resteasy.spi.ResteasyDeployment; import org.junit.rules.ExternalResource; -import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.services.filters.KeycloakSessionServletFilter; -import org.keycloak.services.managers.RealmManager; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.services.resources.KeycloakApplication; -import org.keycloak.services.resources.SaasService; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.ApplicationServlet; +import org.keycloak.testutils.KeycloakServer; /** * @author Stian Thorgersen */ public class KeycloakRule extends ExternalResource { - private String testRealm = "testrealm.json"; - - private UndertowJaxrsServer server; - private KeycloakSessionFactory factory; + private KeycloakServer server; private KeycloakSetup setup; public KeycloakRule() { } - public KeycloakRule(String testRealm) { - this.testRealm = testRealm; - } - public KeycloakRule(KeycloakSetup setup) { this.setup = setup; } protected void before() throws Throwable { - ResteasyDeployment deployment = new ResteasyDeployment(); - deployment.setApplicationClass(KeycloakApplication.class.getName()); - server = new UndertowJaxrsServer().start(); + server = new KeycloakServer(); + server.start(); - DeploymentInfo di = server.undertowDeployment(deployment, "rest"); - di.setClassLoader(getClass().getClassLoader()); - di.setContextPath("/auth-server"); - di.setDeploymentName("Keycloak"); - di.setResourceManager(new ClassPathResourceManager(getClass().getClassLoader(), "META-INF/resources")); - - FilterInfo filter = Servlets.filter("SessionFilter", KeycloakSessionServletFilter.class); - di.addFilter(filter); - di.addFilterUrlMapping("SessionFilter", "/rest/*", DispatcherType.REQUEST); - - server.deploy(di); - - factory = KeycloakApplication.buildSessionFactory(); - - setupDefaultRealm(); - - importRealm(testRealm); + server.importRealm(getClass().getResourceAsStream("/testrealm.json")); if (setup != null) { configure(setup); @@ -114,12 +78,11 @@ public class KeycloakRule extends ExternalResource { servlet.addMapping("/*"); deploymentInfo.addServlet(servlet); - server.deploy(deploymentInfo); + server.getServer().deploy(deploymentInfo); } @Override protected void after() { - factory.close(); server.stop(); } @@ -131,78 +94,31 @@ public class KeycloakRule extends ExternalResource { os.write(c); } byte[] bytes = os.toByteArray(); - System.out.println(new String(bytes)); - return JsonSerialization.fromBytes(RealmRepresentation.class, bytes); } - public void setupDefaultRealm() { - KeycloakSession session = createSession(); - session.getTransaction().begin(); - - RealmManager manager = new RealmManager(session); - - RealmModel defaultRealm = manager.createRealm(RealmModel.DEFAULT_REALM, RealmModel.DEFAULT_REALM); - defaultRealm.setName(RealmModel.DEFAULT_REALM); - defaultRealm.setEnabled(true); - defaultRealm.setTokenLifespan(300); - defaultRealm.setAccessCodeLifespan(60); - defaultRealm.setAccessCodeLifespanUserAction(600); - defaultRealm.setSslNotRequired(false); - defaultRealm.setCookieLoginAllowed(true); - defaultRealm.setRegistrationAllowed(true); - defaultRealm.setAutomaticRegistrationAfterSocialLogin(false); - manager.generateRealmKeys(defaultRealm); - defaultRealm.addRequiredCredential(CredentialRepresentation.PASSWORD); - RoleModel role = defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE); - UserModel admin = defaultRealm.addUser("admin"); - defaultRealm.grantRole(admin, role); - - session.getTransaction().commit(); - session.close(); - } - - public void importRealm(String name) throws IOException { - KeycloakSession session = createSession(); - session.getTransaction().begin(); - - RealmManager manager = new RealmManager(session); - - RealmModel defaultRealm = manager.getRealm(RealmModel.DEFAULT_REALM); - UserModel admin = defaultRealm.getUser("admin"); - - RealmRepresentation rep = loadJson(name); - RealmModel realm = manager.createRealm("test", rep.getRealm()); - manager.importRealm(rep, realm); - realm.addRealmAdmin(admin); - - session.getTransaction().commit(); - session.close(); - } - public void configure(KeycloakSetup configurer) { - KeycloakSession session = createSession(); + KeycloakSession session = server.getKeycloakSessionFactory().createSession(); session.getTransaction().begin(); - RealmManager manager = new RealmManager(session); + try { + RealmManager manager = new RealmManager(session); - RealmModel defaultRealm = manager.getRealm(RealmModel.DEFAULT_REALM); - RealmModel appRealm = manager.getRealm("test"); + RealmModel defaultRealm = manager.getRealm(RealmModel.DEFAULT_REALM); + RealmModel appRealm = manager.getRealm("test"); - configurer.config(manager, defaultRealm, appRealm); + configurer.config(manager, defaultRealm, appRealm); - session.getTransaction().commit(); - session.close(); + session.getTransaction().commit(); + } finally { + session.close(); + } } - public KeycloakSession createSession() { - return factory.createSession(); - } - public interface KeycloakSetup { - + void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm); - + } } diff --git a/testsuite/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java b/testsuite/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java index 7cb41787eb..f4cc01ce89 100755 --- a/testsuite/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java +++ b/testsuite/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java @@ -26,12 +26,15 @@ import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.keycloak.representations.SkeletonKeyToken; import org.keycloak.services.managers.RealmManager; import org.keycloak.models.RealmModel; import org.keycloak.testsuite.DummySocialServlet; +import org.keycloak.testsuite.OAuthClient; +import org.keycloak.testsuite.OAuthClient.AccessTokenResponse; import org.keycloak.testsuite.pages.AppPage; -import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.pages.AppPage.RequestType; +import org.keycloak.testsuite.pages.LoginPage; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup; import org.keycloak.testsuite.rule.WebResource; @@ -65,13 +68,16 @@ public class SocialLoginTest { @WebResource protected LoginPage loginPage; + @WebResource + protected OAuthClient oauth; + @BeforeClass public static void before() { keycloakRule.deployServlet("dummy-social", "/dummy-social", DummySocialServlet.class); } @Test - public void loginSuccess() { + public void loginSuccess() throws Exception { loginPage.open(); loginPage.clickSocial("dummy"); @@ -80,6 +86,15 @@ public class SocialLoginTest { driver.findElement(By.id("submit")).click(); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password"); + + SkeletonKeyToken token = oauth.verifyToken(response.getAccessToken()); + + Assert.assertEquals("dummy-user", token.getPrincipal()); + + Assert.assertEquals(1, token.getRealmAccess().getRoles().size()); + Assert.assertTrue(token.getRealmAccess().isUserInRole("user")); } } diff --git a/testsuite/src/test/resources/testrealm.json b/testsuite/src/test/resources/testrealm.json index 383b425131..f48b969936 100755 --- a/testsuite/src/test/resources/testrealm.json +++ b/testsuite/src/test/resources/testrealm.json @@ -1,4 +1,5 @@ { + "id": "test", "realm": "test", "enabled": true, "tokenLifespan": 300,