diff --git a/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java index 93be310efb..0621b9b040 100644 --- a/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java @@ -105,4 +105,37 @@ public class EventRepresentation { public void setDetails(Map details) { this.details = details; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + EventRepresentation that = (EventRepresentation) o; + + if (time != that.time) return false; + if (type != null ? !type.equals(that.type) : that.type != null) return false; + if (realmId != null ? !realmId.equals(that.realmId) : that.realmId != null) return false; + if (clientId != null ? !clientId.equals(that.clientId) : that.clientId != null) return false; + if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false; + if (sessionId != null ? !sessionId.equals(that.sessionId) : that.sessionId != null) return false; + if (ipAddress != null ? !ipAddress.equals(that.ipAddress) : that.ipAddress != null) return false; + if (error != null ? !error.equals(that.error) : that.error != null) return false; + return !(details != null ? !details.equals(that.details) : that.details != null); + + } + + @Override + public int hashCode() { + int result = (int) (time ^ (time >>> 32)); + result = 31 * result + (type != null ? type.hashCode() : 0); + result = 31 * result + (realmId != null ? realmId.hashCode() : 0); + result = 31 * result + (clientId != null ? clientId.hashCode() : 0); + result = 31 * result + (userId != null ? userId.hashCode() : 0); + result = 31 * result + (sessionId != null ? sessionId.hashCode() : 0); + result = 31 * result + (ipAddress != null ? ipAddress.hashCode() : 0); + result = 31 * result + (error != null ? error.hashCode() : 0); + result = 31 * result + (details != null ? details.hashCode() : 0); + return result; + } } diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml new file mode 100644 index 0000000000..b34fc7d7a3 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml index 97a18f1db8..9ee8e9a467 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml @@ -97,13 +97,102 @@ + + copy-event-queue-provider + generate-resources + + copy + + + + + org.keycloak.testsuite + integration-arquillian-event-queue + ${project.version} + jar + false + ${auth.server.home}/modules/org/keycloak/testsuite/integration-arquillian-event-queue/main + + + + + + install-event-queue-module + generate-resources + + unpack + + + + + org.keycloak.testsuite + integration-arquillian-event-queue + ${project.version} + jar + ${auth.server.home}/modules + **/module.xml + + + + - maven-enforcer-plugin + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + inject-into-keycloak-server-json + process-resources + + run + + + + + + + + + + + + + ant-contrib + ant-contrib + 1.0b3 + + + ant + ant + + + + + org.apache.ant + ant-apache-bsf + 1.9.3 + + + org.apache.bsf + bsf-api + 3.1 + + + rhino + js + 1.7R2 + + + org.keycloak + keycloak-core + ${project.version} + + - maven-antrun-plugin + maven-enforcer-plugin org.codehaus.mojo diff --git a/testsuite/integration-arquillian/servers/auth-server/pom.xml b/testsuite/integration-arquillian/servers/auth-server/pom.xml index c54f112511..a3328bdaef 100644 --- a/testsuite/integration-arquillian/servers/auth-server/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/pom.xml @@ -30,6 +30,7 @@ Auth Server + services jboss undertow diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/pom.xml new file mode 100644 index 0000000000..ad5e007300 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/pom.xml @@ -0,0 +1,81 @@ + + + + + + 4.0.0 + + + org.keycloak.testsuite + integration-arquillian-servers-auth-server-services + 2.0.0.CR1-SNAPSHOT + + + integration-arquillian-event-queue + Auth Server Services - Event Queue + + + + + + + org.keycloak + keycloak-dependencies-server-all + pom + + + + org.keycloak + keycloak-core + + + org.keycloak + keycloak-services + + + io.undertow + undertow-core + + + io.undertow + undertow-servlet + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + src/main/resources + true + + + + diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/AssertEventsServletFilter.java b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/AssertEventsServletFilter.java new file mode 100644 index 0000000000..6d0d4ed198 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/AssertEventsServletFilter.java @@ -0,0 +1,66 @@ +/* + * 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.events; + +import org.keycloak.events.Event; +import org.keycloak.util.JsonSerialization; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.BlockingQueue; + +/** + * @author Marko Strukelj + */ +public class AssertEventsServletFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) servletRequest; + + if ("/event-queue".equals(req.getRequestURI().substring(req.getContextPath().length()))) { + BlockingQueue events = EventsListenerProvider.getInstance(); + HttpServletResponse resp = (HttpServletResponse) servletResponse; + resp.setContentType("application/json"); + + Event event = events.poll(); + if (event != null) { + JsonSerialization.writeValueToStream(servletResponse.getOutputStream(), event); + } else { + resp.setStatus(204); + } + } else { + filterChain.doFilter(servletRequest, servletResponse); + } + } + + @Override + public void destroy() { + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProvider.java new file mode 100644 index 0000000000..a4a1a40051 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProvider.java @@ -0,0 +1,52 @@ +/* + * 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.events; + +import org.keycloak.events.Event; +import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.admin.AdminEvent; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * @author Marko Strukelj + */ +public class EventsListenerProvider implements EventListenerProvider { + + private static final BlockingQueue events = new LinkedBlockingQueue(); + + @Override + public void onEvent(Event event) { + events.add(event); + } + + @Override + public void onEvent(AdminEvent event, boolean includeRepresentation) { + // TODO: implement if needed + } + + @Override + public void close() { + + } + + public static BlockingQueue getInstance() { + return events; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProviderFactory.java new file mode 100644 index 0000000000..aecc01abc8 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsListenerProviderFactory.java @@ -0,0 +1,58 @@ +/* + * 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.events; + +import org.keycloak.Config; +import org.keycloak.events.EventListenerProvider; +import org.keycloak.events.EventListenerProviderFactory; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Marko Strukelj + */ +public class EventsListenerProviderFactory implements EventListenerProviderFactory { + + private static final EventsListenerProvider INSTANCE = new EventsListenerProvider(); + + private EventsServer server = new EventsServer(); + + @Override + public EventListenerProvider create(KeycloakSession session) { + return INSTANCE; + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + server.start(); + } + + @Override + public void close() { + server.stop(); + } + + @Override + public String getId() { + return "event-queue"; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsServer.java b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsServer.java new file mode 100644 index 0000000000..0b04ee3530 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/java/org/keycloak/testsuite/events/EventsServer.java @@ -0,0 +1,95 @@ +/* + * 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.events; + +import io.undertow.Undertow; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.Servlets; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.FilterInfo; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletContainer.Factory; + +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import java.util.logging.Logger; + +/** + * @author Marko Strukelj + */ +public class EventsServer { + + private static final Logger log = Logger.getLogger(EventsServer.class.getName()); + + private String rootPath = "/"; + private int port; + private Undertow server; + + public EventsServer() { + int eventsPort = Integer.parseInt(System.getProperty("auth.server.events.http.port", "8089")); + int portOffset = Integer.parseInt(System.getProperty("auth.server.port.offset", "0")); + int jbossPortOffset = Integer.parseInt(System.getProperty("jboss.socket.binding.port-offset", "-1")); + + log.fine("Configuration:"); + log.fine(" auth.server.events.http.port: " + eventsPort); + log.fine(" auth.server.port.offset: " + portOffset); + log.fine(" jboss.socket.binding.port-offset: " + jbossPortOffset); + port = eventsPort + (jbossPortOffset != -1 ? jbossPortOffset : portOffset); + } + + public void start() { + + PathHandler root = new PathHandler(); + this.server = Undertow.builder().addHttpListener(port, "localhost").setHandler(root).build(); + this.server.start(); + + ServletContainer container = Factory.newInstance(); + + DeploymentInfo di = new DeploymentInfo(); + di.setClassLoader(getClass().getClassLoader()); + di.setContextPath(rootPath); + di.setDeploymentName("testing-event-queue"); + + FilterInfo filter = Servlets.filter("EventsFilter", AssertEventsServletFilter.class); + di.addFilter(filter); + di.addFilterUrlMapping("EventsFilter", "/event-queue", DispatcherType.REQUEST); + + DeploymentManager manager = container.addDeployment(di); + manager.deploy(); + + try { + root.addPrefixPath(rootPath, manager.start()); + } catch (ServletException e) { + throw new RuntimeException(e); + } + log.info("Started EventsServer on port: " + port); + } + + public void stop() { + this.server.stop(); + } + + public void setRootPath(String rootPath) { + this.rootPath = rootPath; + } + + public void setPort(int port) { + this.port = port; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory new file mode 100644 index 0000000000..e995149b93 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactory @@ -0,0 +1,35 @@ +# +# 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. +# + +# +# 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. +# + +org.keycloak.testsuite.events.EventsListenerProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/org/keycloak/testsuite/integration-arquillian-event-queue/main/module.xml b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/org/keycloak/testsuite/integration-arquillian-event-queue/main/module.xml new file mode 100644 index 0000000000..19f3d4194c --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/event-queue/src/main/resources/org/keycloak/testsuite/integration-arquillian-event-queue/main/module.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/services/pom.xml b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml new file mode 100644 index 0000000000..2d96ac0fba --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/pom.xml @@ -0,0 +1,36 @@ + + + + + + org.keycloak.testsuite + integration-arquillian-servers-auth-server + 2.0.0.CR1-SNAPSHOT + + 4.0.0 + + integration-arquillian-servers-auth-server-services + pom + Auth Server Services + + + event-queue + + + diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 39d376a9c8..05625a18e1 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -57,6 +57,11 @@ commons-configuration 1.10 + + org.keycloak.testsuite + integration-arquillian-event-queue + ${project.version} + diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java new file mode 100644 index 0000000000..8e4c92ac85 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java @@ -0,0 +1,365 @@ +/* + * 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; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.junit.Assert; +import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; +import org.keycloak.common.util.PemUtils; +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.representations.idm.UserSessionRepresentation; +import org.keycloak.util.JsonSerialization; +import org.keycloak.util.TokenUtil; + +import java.io.IOException; +import java.security.PublicKey; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Stian Thorgersen + */ +public class AssertEvents { + + static final String DEFAULT_CLIENT_ID = "test-app"; + static final String DEFAULT_IP_ADDRESS = "127.0.0.1"; + static final String DEFAULT_REALM = "test"; + static final String DEFAULT_USERNAME = "test-user@localhost"; + + String defaultRedirectUri = "http://localhost:8081/app/auth"; + String defaultEventsQueueUri = "http://localhost:8092"; + + private RealmResource realmResource; + private RealmRepresentation realmRep; + private AbstractKeycloakTest context; + private PublicKey realmPublicKey; + private UserRepresentation defaultUser; + + public AssertEvents(AbstractKeycloakTest ctx) throws Exception { + context = ctx; + + realmResource = context.adminClient.realms().realm(DEFAULT_REALM); + realmRep = realmResource.toRepresentation(); + String pubKeyString = realmRep.getPublicKey(); + realmPublicKey = PemUtils.decodePublicKey(pubKeyString); + + defaultUser = getUser(DEFAULT_USERNAME); + if (defaultUser == null) { + throw new RuntimeException("Default user does not exist: " + DEFAULT_USERNAME + ". Make sure to add it to your test realm."); + } + + defaultEventsQueueUri = getAuthServerEventsQueueUri(); + } + + String getAuthServerEventsQueueUri() { + int httpPort = Integer.parseInt(System.getProperty("auth.server.event.http.port", "8089")); + int portOffset = Integer.parseInt(System.getProperty("auth.server.port.offset", "0")); + return "http://localhost:" + (httpPort + portOffset); + } + + public EventRepresentation poll() { + EventRepresentation event = fetchNextEvent(); + Assert.assertNotNull("Event expected", event); + + return event; + } + + public void clear() { + realmResource.clearEvents(); + } + + public ExpectedEvent expectRequiredAction(EventType event) { + return expectLogin().event(event).removeDetail(Details.CONSENT).session(isUUID()); + } + + public ExpectedEvent expectLogin() { + return expect(EventType.LOGIN) + .detail(Details.CODE_ID, isCodeId()) + //.detail(Details.USERNAME, DEFAULT_USERNAME) + //.detail(Details.AUTH_METHOD, OIDCLoginProtocol.LOGIN_PROTOCOL) + //.detail(Details.AUTH_TYPE, AuthorizationEndpoint.CODE_AUTH_TYPE) + .detail(Details.REDIRECT_URI, defaultRedirectUri) + .detail(Details.CONSENT, Details.CONSENT_VALUE_NO_CONSENT_REQUIRED) + .session(isUUID()); + } + + public ExpectedEvent expectClientLogin() { + return expect(EventType.CLIENT_LOGIN) + .detail(Details.CODE_ID, isCodeId()) + .detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID) + .detail(Details.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS) + .removeDetail(Details.CODE_ID) + .session(isUUID()); + } + + public ExpectedEvent expectSocialLogin() { + return expect(EventType.LOGIN) + .detail(Details.CODE_ID, isCodeId()) + .detail(Details.USERNAME, DEFAULT_USERNAME) + .detail(Details.AUTH_METHOD, "form") + .detail(Details.REDIRECT_URI, defaultRedirectUri) + .session(isUUID()); + } + + public ExpectedEvent expectCodeToToken(String codeId, String sessionId) { + return expect(EventType.CODE_TO_TOKEN) + .detail(Details.CODE_ID, codeId) + .detail(Details.TOKEN_ID, isUUID()) + .detail(Details.REFRESH_TOKEN_ID, isUUID()) + .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH) + .detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID) + .session(sessionId); + } + + public ExpectedEvent expectRefresh(String refreshTokenId, String sessionId) { + return expect(EventType.REFRESH_TOKEN) + .detail(Details.TOKEN_ID, isUUID()) + .detail(Details.REFRESH_TOKEN_ID, refreshTokenId) + .detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH) + .detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID()) + .detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID) + .session(sessionId); + } + + public ExpectedEvent expectLogout(String sessionId) { + return expect(EventType.LOGOUT).client((String) null) + .detail(Details.REDIRECT_URI, defaultRedirectUri) + .session(sessionId); + } + + public ExpectedEvent expectRegister(String username, String email) { + UserRepresentation user = username != null ? getUser(username) : null; + return expect(EventType.REGISTER) + .user(user != null ? user.getId() : null) + .detail(Details.USERNAME, username) + .detail(Details.EMAIL, email) + .detail(Details.REGISTER_METHOD, "form") + .detail(Details.REDIRECT_URI, defaultRedirectUri); + } + + public ExpectedEvent expectAccount(EventType event) { + return expect(event).client("account"); + } + + public ExpectedEvent expect(EventType event) { + return new ExpectedEvent() + .realm(realmRep.getId()) + .client(DEFAULT_CLIENT_ID) + .user(defaultUser.getId()) + .ipAddress(DEFAULT_IP_ADDRESS) + .session((String) null) + .event(event); + } + + UserRepresentation getUser(String username) { + List result = realmResource.users().search(username, null, null, null, 0, 1); + return result.size() > 0 ? result.get(0) : null; + } + + public PublicKey getRealmPublicKey() { + return realmPublicKey; + } + + public class ExpectedEvent { + private EventRepresentation expected = new EventRepresentation(); + private Matcher userId; + private Matcher sessionId; + private HashMap> details; + + public ExpectedEvent realm(RealmRepresentation realm) { + expected.setRealmId(realm.getId()); + return this; + } + + public ExpectedEvent realm(String realmId) { + expected.setRealmId(realmId); + return this; + } + + public ExpectedEvent client(ClientRepresentation client) { + expected.setClientId(client.getClientId()); + return this; + } + + public ExpectedEvent client(String clientId) { + expected.setClientId(clientId); + return this; + } + + public ExpectedEvent user(UserRepresentation user) { + return user(user.getId()); + } + + public ExpectedEvent user(String userId) { + return user(CoreMatchers.equalTo(userId)); + } + + public ExpectedEvent user(Matcher userId) { + this.userId = userId; + return this; + } + + public ExpectedEvent session(UserSessionRepresentation session) { + return session(session.getId()); + } + + public ExpectedEvent session(String sessionId) { + return session(CoreMatchers.equalTo(sessionId)); + } + + public ExpectedEvent session(Matcher sessionId) { + this.sessionId = sessionId; + return this; + } + + public ExpectedEvent ipAddress(String ipAddress) { + expected.setIpAddress(ipAddress); + return this; + } + + public ExpectedEvent event(EventType e) { + expected.setType(e.name()); + return this; + } + + public ExpectedEvent detail(String key, String value) { + return detail(key, CoreMatchers.equalTo(value)); + } + + public ExpectedEvent detail(String key, Matcher matcher) { + if (details == null) { + details = new HashMap>(); + } + details.put(key, matcher); + return this; + } + + public ExpectedEvent removeDetail(String key) { + if (details != null) { + details.remove(key); + } + return this; + } + + public ExpectedEvent clearDetails() { + if (details != null) details.clear(); + return this; + } + + public ExpectedEvent error(String error) { + expected.setError(error); + return this; + } + + public EventRepresentation assertEvent() { + return assertEvent(poll()); + } + + public EventRepresentation assertEvent(EventRepresentation actual) { + if (expected.getError() != null && !expected.getType().toString().endsWith("_ERROR")) { + expected.setType(expected.getType() + "_ERROR"); + } + Assert.assertEquals(expected.getType(), actual.getType()); + Assert.assertEquals(expected.getRealmId(), actual.getRealmId()); + Assert.assertEquals(expected.getClientId(), actual.getClientId()); + Assert.assertEquals(expected.getError(), actual.getError()); + Assert.assertEquals(expected.getIpAddress(), actual.getIpAddress()); + Assert.assertThat(actual.getUserId(), userId); + Assert.assertThat(actual.getSessionId(), sessionId); + + if (details == null || details.isEmpty()) { +// Assert.assertNull(actual.getDetails()); + } else { + Assert.assertNotNull(actual.getDetails()); + for (Map.Entry> d : details.entrySet()) { + String actualValue = actual.getDetails().get(d.getKey()); + if (!actual.getDetails().containsKey(d.getKey())) { + Assert.fail(d.getKey() + " missing"); + } + + Assert.assertThat("Unexpected value for " + d.getKey(), actualValue, d.getValue()); + } + /* + for (String k : actual.getDetails().keySet()) { + if (!details.containsKey(k)) { + Assert.fail(k + " was not expected"); + } + } + */ + } + + return actual; + } + } + + public static Matcher isCodeId() { + return isUUID(); + } + + public static Matcher isUUID() { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(String item) { + return 36 == item.length() && item.charAt(8) == '-' && item.charAt(13) == '-' && item.charAt(18) == '-' && item.charAt(23) == '-'; + } + + @Override + public void describeTo(Description description) { + description.appendText("Not an UUID"); + } + }; + } + + private EventRepresentation fetchNextEvent() { + CloseableHttpClient httpclient = HttpClients.createDefault(); + try { + HttpPost post = new HttpPost(defaultEventsQueueUri + "/event-queue"); + CloseableHttpResponse response = httpclient.execute(post); + if (response.getStatusLine().getStatusCode() != 200) { + throw new RuntimeException("Failed to retrieve event from " + post.getURI() + ": " + response.getStatusLine().toString() + " / " + IOUtils.toString(response.getEntity().getContent())); + } + + return JsonSerialization.readValue(response.getEntity().getContent(), EventRepresentation.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + finally { + try { + httpclient.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java index ba50fe7227..597556cea5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java @@ -38,7 +38,7 @@ public abstract class AbstractEventTest extends AbstractAuthTest { configRep.setAdminEventsDetailsEnabled(false); configRep.setAdminEventsEnabled(false); configRep.setEventsEnabled(false); - configRep.setEnabledEventTypes(Collections.EMPTY_LIST); // resets to all types + configRep.setEnabledEventTypes(Collections.emptyList()); // resets to all types saveConfig(); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index ae0f5f9a07..f5d64a511a 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -11,7 +11,8 @@ "jboss-logging" : { "success-level": "debug", "error-level": "warn" - } + }, + "event-queue": {} }, "realm": { diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json new file mode 100644 index 0000000000..845adda955 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json @@ -0,0 +1,186 @@ +{ + "id": "test", + "realm": "test", + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "resetPasswordAllowed": true, + "editUsernameAllowed" : 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" ], + "defaultRoles": [ "user" ], + "smtpServer": { + "from": "auto@keycloak.org", + "host": "localhost", + "port":"3025" + }, + "users" : [ + { + "username" : "test-user@localhost", + "enabled": true, + "email" : "test-user@localhost", + "firstName": "Tom", + "lastName": "Brady", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user", "offline_access"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "john-doh@localhost", + "enabled": true, + "email" : "john-doh@localhost", + "firstName": "John", + "lastName": "Doh", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "keycloak-user@localhost", + "enabled": true, + "email" : "keycloak-user@localhost", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "realmRoles": ["user"], + "clientRoles": { + "test-app": [ "customer-user" ], + "account": [ "view-profile", "manage-account" ] + } + }, + { + "username" : "topGroupUser", + "enabled": true, + "email" : "top@redhat.com", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "groups": [ + "/topGroup" + ] + }, + { + "username" : "level2GroupUser", + "enabled": true, + "email" : "level2@redhat.com", + "credentials" : [ + { "type" : "password", + "value" : "password" } + ], + "groups": [ + "/topGroup/level2group" + ] + } + ], + "scopeMappings": [ + { + "client": "third-party", + "roles": ["user"] + }, + { + "client": "test-app", + "roles": ["user"] + } + ], + "clients": [ + { + "clientId": "test-app", + "enabled": true, + "baseUrl": "http://localhost:8081/app", + "redirectUris": [ + "http://localhost:8081/app/*" + ], + "adminUrl": "http://localhost:8081/app/logout", + "secret": "password" + }, + { + "clientId" : "third-party", + "enabled": true, + "consentRequired": true, + + "redirectUris": [ + "http://localhost:8081/app/*" + ], + "secret": "password" + } + ], + "roles" : { + "realm" : [ + { + "name": "user", + "description": "Have User privileges" + }, + { + "name": "admin", + "description": "Have Administrator privileges" + } + ], + "client" : { + "test-app" : [ + { + "name": "customer-user", + "description": "Have Customer User privileges" + }, + { + "name": "customer-admin", + "description": "Have Customer Admin privileges" + } + ] + } + + }, + "groups" : [ + { + "name": "topGroup", + "attributes": { + "topAttribute": ["true"] + + }, + "realmRoles": ["user"], + + "subGroups": [ + { + "name": "level2group", + "realmRoles": ["admin"], + "clientRoles": { + "test-app": ["customer-user"] + }, + "attributes": { + "level2Attribute": ["true"] + + } + } + ] + } + ], + + + "clientScopeMappings": { + "test-app": [ + { + "client": "third-party", + "roles": ["customer-user"] + } + ] + }, + + "internationalizationEnabled": true, + "supportedLocales": ["en", "de"], + "defaultLocale": "en", + "eventsListeners": ["jboss-logging", "event-queue"] +} diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 015f5336dc..2cb09d539d 100644 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -48,6 +48,7 @@ 100 8180 + 8089 8543 10090 10099 @@ -140,6 +141,7 @@ ${auth.server.port.offset} ${auth.server.http.port} + ${auth.server.events.http.port} ${auth.server.https.port} ${auth.server.management.port} ${auth.server.management.port.jmx}