keycloak-aplcache
Changes
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java 49(+49 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java 103(+103 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AbstractEventTest.java 57(+57 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AdminEventTest.java 111(+111 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/EventConfigTest.java 126(+126 -0)
Details
diff --git a/core/src/main/java/org/keycloak/representations/idm/AdminEventRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AdminEventRepresentation.java
new file mode 100644
index 0000000..f615cbd
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AdminEventRepresentation.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.representations.idm;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class AdminEventRepresentation {
+
+ private long time;
+ private String realmId;
+ private AuthDetailsRepresentation authDetails;
+ private String operationType;
+ private String resourcePath;
+ private String representation;
+ private String error;
+
+ public long getTime() {
+ return time;
+ }
+
+ public void setTime(long time) {
+ this.time = time;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public AuthDetailsRepresentation getAuthDetails() {
+ return authDetails;
+ }
+
+ public void setAuthDetails(AuthDetailsRepresentation authDetails) {
+ this.authDetails = authDetails;
+ }
+
+ public String getOperationType() {
+ return operationType;
+ }
+
+ public void setOperationType(String operationType) {
+ this.operationType = operationType;
+ }
+
+ public String getResourcePath() {
+ return resourcePath;
+ }
+
+ public void setResourcePath(String resourcePath) {
+ this.resourcePath = resourcePath;
+ }
+
+ public String getRepresentation() {
+ return representation;
+ }
+
+ public void setRepresentation(String representation) {
+ this.representation = representation;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthDetailsRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthDetailsRepresentation.java
new file mode 100644
index 0000000..746aa71
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthDetailsRepresentation.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.representations.idm;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class AuthDetailsRepresentation {
+
+ private String realmId;
+ private String clientId;
+ private String userId;
+ private String ipAddress;
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java
new file mode 100644
index 0000000..93be310
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/idm/EventRepresentation.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.representations.idm;
+
+import java.util.Map;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class EventRepresentation {
+
+ private long time;
+ private String type;
+ private String realmId;
+ private String clientId;
+ private String userId;
+ private String sessionId;
+ private String ipAddress;
+ private String error;
+ private Map<String, String> details;
+
+ public long getTime() {
+ return time;
+ }
+
+ public void setTime(long time) {
+ this.time = time;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ public void setSessionId(String sessionId) {
+ this.sessionId = sessionId;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public Map<String, String> getDetails() {
+ return details;
+ }
+
+ public void setDetails(Map<String, String> details) {
+ this.details = details;
+ }
+}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index 0242d24..87f6763 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -19,6 +19,7 @@ package org.keycloak.admin.client.resource;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@@ -26,6 +27,8 @@ import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.List;
import java.util.Map;
+import org.keycloak.representations.idm.AdminEventRepresentation;
+import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
/**
* @author rodrigo.sasaki@icarros.com.br
@@ -61,6 +64,52 @@ public interface RealmResource {
@Path("groups")
GroupsResource groups();
+ @DELETE
+ @Path("events")
+ void clearEvents();
+
+ @GET
+ @Path("events")
+ @Produces(MediaType.APPLICATION_JSON)
+ List<EventRepresentation> getEvents();
+
+ @Path("events")
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<EventRepresentation> getEvents(@QueryParam("type") List<String> types, @QueryParam("client") String client,
+ @QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
+ @QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
+ @QueryParam("max") Integer maxResults);
+
+ @DELETE
+ @Path("admin-events")
+ void clearAdminEvents();
+
+ @GET
+ @Path("admin-events")
+ @Produces(MediaType.APPLICATION_JSON)
+ List<AdminEventRepresentation> getAdminEvents();
+
+ @GET
+ @Path("admin-events")
+ @Produces(MediaType.APPLICATION_JSON)
+ List<AdminEventRepresentation> getAdminEvents(@QueryParam("operationTypes") List<String> operationTypes, @QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
+ @QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
+ @QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
+ @QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
+ @QueryParam("max") Integer maxResults);
+
+ @GET
+ @Path("events/config")
+ @Produces(MediaType.APPLICATION_JSON)
+ public RealmEventsConfigRepresentation getRealmEventsConfig();
+
+ @PUT
+ @Path("events/config")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void updateRealmEventsConfig(RealmEventsConfigRepresentation rep);
+
@GET
@Path("group-by-path/{path: .*}")
@NoCache
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 868aa10..30f7d5f 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -17,6 +17,9 @@
package org.keycloak.models.utils;
+import org.keycloak.events.Event;
+import org.keycloak.events.admin.AdminEvent;
+import org.keycloak.events.admin.AuthDetails;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
@@ -41,12 +44,15 @@ import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.representations.idm.AdminEventRepresentation;
+import org.keycloak.representations.idm.AuthDetailsRepresentation;
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientTemplateRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
@@ -167,6 +173,43 @@ public class ModelToRepresentation {
return rep;
}
+ public static EventRepresentation toRepresentation(Event event) {
+ EventRepresentation rep = new EventRepresentation();
+ rep.setTime(event.getTime());
+ rep.setType(event.getType().toString());
+ rep.setRealmId(event.getRealmId());
+ rep.setClientId(event.getClientId());
+ rep.setUserId(event.getUserId());
+ rep.setSessionId(event.getSessionId());
+ rep.setError(event.getError());
+ rep.setDetails(event.getDetails());
+ return rep;
+ }
+
+ public static AdminEventRepresentation toRepresentation(AdminEvent adminEvent) {
+ AdminEventRepresentation rep = new AdminEventRepresentation();
+ rep.setTime(adminEvent.getTime());
+ rep.setRealmId(adminEvent.getRealmId());
+ if (adminEvent.getAuthDetails() != null) {
+ rep.setAuthDetails(toRepresentation(adminEvent.getAuthDetails()));
+ }
+ rep.setOperationType(adminEvent.getOperationType().toString());
+ rep.setResourcePath(adminEvent.getResourcePath());
+ rep.setRepresentation(adminEvent.getRepresentation());
+ rep.setError(adminEvent.getError());
+
+ return rep;
+ }
+
+ public static AuthDetailsRepresentation toRepresentation(AuthDetails authDetails) {
+ AuthDetailsRepresentation rep = new AuthDetailsRepresentation();
+ rep.setRealmId(authDetails.getRealmId());
+ rep.setClientId(authDetails.getClientId());
+ rep.setUserId(authDetails.getUserId());
+ rep.setIpAddress(authDetails.getIpAddress());
+ return rep;
+ }
+
public static RoleRepresentation toRepresentation(RoleModel role) {
RoleRepresentation rep = new RoleRepresentation();
rep.setId(role.getId());
@@ -383,15 +426,15 @@ public class ModelToRepresentation {
if (realm.getEventsListeners() != null) {
rep.setEventsListeners(new LinkedList<>(realm.getEventsListeners()));
}
-
+
if(realm.getEnabledEventTypes() != null) {
rep.setEnabledEventTypes(new LinkedList<>(realm.getEnabledEventTypes()));
}
-
+
rep.setAdminEventsEnabled(realm.isAdminEventsEnabled());
-
+
rep.setAdminEventsDetailsEnabled(realm.isAdminEventsDetailsEnabled());
-
+
return rep;
}
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 22eb5db..15b0e81 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -45,8 +45,11 @@ import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.adapters.action.GlobalRequestResult;
+import org.keycloak.representations.idm.AdminEventRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.PartialImportRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
@@ -75,6 +78,7 @@ import javax.ws.rs.core.UriInfo;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
@@ -82,7 +86,6 @@ import java.util.List;
import java.util.Map;
import java.util.regex.PatternSyntaxException;
import org.keycloak.partialimport.PartialImportManager;
-import org.keycloak.representations.idm.PartialImportRepresentation;
/**
* Base resource class for the admin REST api of one realm
@@ -428,6 +431,7 @@ public class RealmAdminResource {
*
* Returns all events, or filters them based on URL query parameters listed here
*
+ * @param types The types of events to return
* @param client App or oauth client name
* @param user User id
* @param ipAddress IP address
@@ -441,7 +445,7 @@ public class RealmAdminResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List<Event> getEvents(@QueryParam("client") String client,
+ public List<EventRepresentation> getEvents(@QueryParam("type") List<String> types, @QueryParam("client") String client,
@QueryParam("user") String user, @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo,
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults) {
@@ -454,8 +458,7 @@ public class RealmAdminResource {
query.client(client);
}
- List<String> types = uriInfo.getQueryParameters().get("type");
- if (types != null) {
+ if (types != null & !types.isEmpty()) {
EventType[] t = new EventType[types.size()];
for (int i = 0; i < t.length; i++) {
t[i] = EventType.valueOf(types.get(i));
@@ -499,7 +502,15 @@ public class RealmAdminResource {
query.maxResults(maxResults);
}
- return query.getResultList();
+ return toEventListRep(query.getResultList());
+ }
+
+ private List<EventRepresentation> toEventListRep(List<Event> events) {
+ List<EventRepresentation> reps = new ArrayList<>();
+ for (Event event : events) {
+ reps.add(ModelToRepresentation.toRepresentation(event));
+ }
+ return reps;
}
/**
@@ -507,6 +518,7 @@ public class RealmAdminResource {
*
* Returns all admin events, or filters events based on URL query parameters listed here
*
+ * @param operationTypes
* @param authRealm
* @param authClient
* @param authUser user id
@@ -514,7 +526,6 @@ public class RealmAdminResource {
* @param resourcePath
* @param dateTo
* @param dateFrom
- * @param resourcePath
* @param firstResult
* @param maxResults
* @return
@@ -523,7 +534,7 @@ public class RealmAdminResource {
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
- public List<AdminEvent> getEvents(@QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
+ public List<AdminEventRepresentation> getEvents(@QueryParam("operationTypes") List<String> operationTypes, @QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
@QueryParam("authUser") String authUser, @QueryParam("authIpAddress") String authIpAddress,
@QueryParam("resourcePath") String resourcePath, @QueryParam("dateFrom") String dateFrom,
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
@@ -553,8 +564,7 @@ public class RealmAdminResource {
query.resourcePath(resourcePath);
}
- List<String> operationTypes = uriInfo.getQueryParameters().get("operationTypes");
- if (operationTypes != null) {
+ if (operationTypes != null && !operationTypes.isEmpty()) {
OperationType[] t = new OperationType[operationTypes.size()];
for (int i = 0; i < t.length; i++) {
t[i] = OperationType.valueOf(operationTypes.get(i));
@@ -591,7 +601,16 @@ public class RealmAdminResource {
query.maxResults(maxResults);
}
- return query.getResultList();
+ return toAdminEventRep(query.getResultList());
+ }
+
+ private List<AdminEventRepresentation> toAdminEventRep(List<AdminEvent> events) {
+ List<AdminEventRepresentation> reps = new ArrayList<>();
+ for (AdminEvent event : events) {
+ reps.add(ModelToRepresentation.toRepresentation(event));
+ }
+
+ return reps;
}
/**
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
index 345d053..92c5c24 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.adapter.example;
+import org.apache.commons.io.FileUtils;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.shrinkwrap.api.spec.WebArchive;
@@ -26,6 +27,7 @@ import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.adapter.AbstractExampleAdapterTest;
import org.keycloak.testsuite.adapter.page.CustomerPortalExample;
import org.keycloak.testsuite.adapter.page.DatabaseServiceExample;
@@ -43,6 +45,8 @@ import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -269,4 +273,103 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']"));
resultList.get(0).findElement(By.xpath(".//td[text()='consent']/../td[text()='consent_granted']"));
}
+
+ @Test
+ public void historyOfAccessResourceTest() throws IOException {
+ RealmRepresentation realm = testRealmResource().toRepresentation();
+ realm.setEventsEnabled(true);
+ realm.setEnabledEventTypes(Arrays.asList("LOGIN", "LOGIN_ERROR", "LOGOUT", "CODE_TO_TOKEN"));
+ testRealmResource().update(realm);
+
+ customerPortalExamplePage.navigateTo();
+ customerPortalExamplePage.customerListing();
+
+ testRealmLoginPage.form().login("bburke@redhat.com", "password");
+
+ Assert.assertTrue(driver.getPageSource().contains("Username: bburke@redhat.com")
+ && driver.getPageSource().contains("Bill Burke")
+ && driver.getPageSource().contains("Stian Thorgersen")
+ );
+
+ if (isRelative()) { //KEYCLOAK-1546
+ productPortalExamplePage.logOut();
+ } else {
+ driver.navigate().to(testRealmPage.getOIDCLogoutUrl() + "?redirect_uri=" + productPortalExamplePage);
+ }
+
+ loginEventsPage.navigateTo();
+
+ if (!testContext.isAdminLoggedIn()) {
+ loginPage.form().login(adminUser);
+ testContext.setAdminLoggedIn(true);
+ }
+
+ loginEventsPage.table().filter();
+ loginEventsPage.table().filterForm().addEventType("LOGOUT");
+ loginEventsPage.table().update();
+
+ List<WebElement> resultList = loginEventsPage.table().rows();
+
+ assertEquals(1, resultList.size());
+
+ resultList.get(0).findElement(By.xpath(".//td[text()='LOGOUT']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+
+ loginEventsPage.table().reset();
+ loginEventsPage.table().filterForm().addEventType("LOGIN");
+ loginEventsPage.table().update();
+ resultList = loginEventsPage.table().rows();
+
+ assertEquals(1, resultList.size());
+
+ resultList.get(0).findElement(By.xpath(".//td[text()='LOGIN']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='username']/../td[text()='bburke@redhat.com']"));
+
+ loginEventsPage.table().reset();
+ loginEventsPage.table().filterForm().addEventType("CODE_TO_TOKEN");
+ loginEventsPage.table().update();
+ resultList = loginEventsPage.table().rows();
+
+ assertEquals(1, resultList.size());
+ resultList.get(0).findElement(By.xpath(".//td[text()='CODE_TO_TOKEN']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='Client']/../td[text()='customer-portal']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='IP Address']/../td[text()='127.0.0.1']"));
+ resultList.get(0).findElement(By.xpath(".//td[text()='refresh_token_type']/../td[text()='Refresh']"));
+
+ String serverLogPath = null;
+
+ if (System.getProperty("app.server.wildfly", "false").equals("true")) {
+ serverLogPath = System.getProperty("app.server.wildfly.home") + "/standalone/log/server.log";
+ }
+
+ if (System.getProperty("app.server.eap6", "false").equals("true")) {
+ serverLogPath = System.getProperty("app.server.eap6.home") + "/standalone/log/server.log";
+ }
+
+ if (System.getProperty("app.server.eap7", "false").equals("true")) {
+ serverLogPath = System.getProperty("app.server.eap7.home") + "/standalone/log/server.log";
+ }
+
+ String appServerUrl;
+ if (Boolean.parseBoolean(System.getProperty("app.server.ssl.required"))) {
+ appServerUrl = "https://localhost:" + System.getProperty("app.server.https.port", "8543") + "/";
+ } else {
+ appServerUrl = "http://localhost:" + System.getProperty("app.server.http.port", "8280") + "/";
+ }
+
+ if (serverLogPath != null) {
+ File serverLog = new File(serverLogPath);
+ String serverLogContent = FileUtils.readFileToString(serverLog);
+ UserRepresentation bburke = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com");
+
+ Pattern pattern = Pattern.compile("User '" + bburke.getId() + "' invoking '" + appServerUrl + "customer-portal\\/customers\\/view\\.jsp[^\\s]+' on client 'customer-portal'");
+ Matcher matcher = pattern.matcher(serverLogContent);
+
+ assertTrue(matcher.find());
+ assertTrue(serverLogContent.contains("User '" + bburke.getId() + "' invoking '" + appServerUrl + "database/customers' on client 'database-service'"));
+ }
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AbstractEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AbstractEventTest.java
new file mode 100644
index 0000000..77c61b3
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AbstractEventTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.event;
+
+import java.util.Collections;
+import org.junit.Before;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
+import org.keycloak.testsuite.AbstractAuthTest;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public abstract class AbstractEventTest extends AbstractAuthTest {
+
+ protected RealmEventsConfigRepresentation configRep;
+
+ @Before
+ public void setConfigRep() {
+ RealmResource testRsc = testRealmResource();
+ configRep = testRsc.getRealmEventsConfig();
+ configRep.setAdminEventsDetailsEnabled(false);
+ configRep.setAdminEventsEnabled(false);
+ configRep.setEventsEnabled(false);
+ configRep.setEnabledEventTypes(Collections.EMPTY_LIST); // resets to all types
+ saveConfig();
+ }
+
+ protected void saveConfig() {
+ RealmResource testRsc = testRealmResource();
+ testRsc.updateRealmEventsConfig(configRep);
+ configRep = testRsc.getRealmEventsConfig();
+ }
+
+ protected void enableEvents() {
+ configRep.setEventsEnabled(true);
+ configRep.setAdminEventsEnabled(Boolean.TRUE);
+
+ saveConfig();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AdminEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AdminEventTest.java
new file mode 100644
index 0000000..bdf699a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AdminEventTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.event;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.representations.idm.AdminEventRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.testsuite.admin.ApiUtil;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test getting and filtering admin events.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class AdminEventTest extends AbstractEventTest {
+
+ @Before
+ public void initConfig() {
+ enableEvents();
+ testRealmResource().clearAdminEvents();
+ }
+
+ private List<AdminEventRepresentation> events() {
+ return testRealmResource().getAdminEvents();
+ }
+
+ private void createUser(String username) {
+ UserRepresentation user = createUserRepresentation(username, username + "@foo.com", "foo", "bar", true);
+ ApiUtil.createUserWithAdminClient(testRealmResource(), user);
+ }
+
+ private void updateRealm() {
+ RealmRepresentation realm = testRealmResource().toRepresentation();
+ realm.setDisplayName("Fury Road");
+ testRealmResource().update(realm);
+ }
+
+ private String realmName() {
+ return testRealmResource().toRepresentation().getId();
+ }
+
+ @Test
+ public void clearAdminEventsTest() {
+ createUser("user0");
+ assertEquals(1, events().size());
+ testRealmResource().clearAdminEvents();
+ assertEquals(Collections.EMPTY_LIST, events());
+ }
+
+ @Test
+ public void retrieveAdminEventTest() {
+ createUser("user1");
+ List<AdminEventRepresentation> events = events();
+
+ assertEquals(1, events.size());
+ AdminEventRepresentation event = events().get(0);
+ assertEquals("CREATE", event.getOperationType());
+
+ assertEquals(realmName(), event.getRealmId());
+ assertEquals(realmName(), event.getAuthDetails().getRealmId());
+ assertNull(event.getRepresentation());
+ }
+
+ @Test
+ public void testGetRepresentation() {
+ configRep.setAdminEventsDetailsEnabled(Boolean.TRUE);
+ saveConfig();
+
+ createUser("user2");
+ AdminEventRepresentation event = events().get(0);
+ assertNotNull(event.getRepresentation());
+ assertTrue(event.getRepresentation().contains("foo"));
+ }
+
+ @Test
+ public void testFilterAdminEvents() {
+ // two CREATE and one UPDATE
+ createUser("user3");
+ createUser("user4");
+ updateRealm();
+ assertEquals(3, events().size());
+
+ List<AdminEventRepresentation> events = testRealmResource().getAdminEvents(Arrays.asList("CREATE"), realmName(), null, null, null, null, null, null, null, null);
+ assertEquals(2, events.size());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/EventConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/EventConfigTest.java
new file mode 100644
index 0000000..a2bf746
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/EventConfigTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.event;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test updates to the events configuration.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class EventConfigTest extends AbstractEventTest {
+
+ @Test
+ public void defaultEventConfigTest() {
+ assertFalse(configRep.isAdminEventsDetailsEnabled());
+ assertFalse(configRep.isAdminEventsEnabled());
+ assertFalse(configRep.isEventsEnabled());
+
+ List<String> eventListeners = configRep.getEventsListeners();
+ assertEquals(1, eventListeners.size());
+ assertEquals("jboss-logging", eventListeners.get(0));
+ }
+
+ @Test
+ public void enableEventsTest() {
+ enableEvents();
+
+ assertTrue(configRep.isEventsEnabled());
+ assertTrue(configRep.isAdminEventsEnabled());
+ }
+
+ @Test
+ public void addRemoveListenerTest() {
+ configRep.setEventsListeners(Collections.EMPTY_LIST);
+ saveConfig();
+ assertEquals(0, configRep.getEventsListeners().size());
+
+ configRep.setEventsListeners(Arrays.asList("email"));
+ saveConfig();
+ List<String> eventListeners = configRep.getEventsListeners();
+ assertEquals(1, eventListeners.size());
+ assertEquals("email", eventListeners.get(0));
+ }
+
+ @Test
+ public void loginEventSettingsTest() {
+ enableEvents();
+
+ assertTrue(hasEventType("LOGIN"));
+ assertTrue(hasEventType("LOGOUT"));
+ assertTrue(hasEventType("CLIENT_DELETE_ERROR"));
+
+ int defaultEventCount = configRep.getEnabledEventTypes().size();
+
+ configRep.setEnabledEventTypes(Arrays.asList("CLIENT_DELETE", "CLEINT_DELETE_ERROR"));
+ saveConfig();
+
+ List<String> enabledEventTypes = configRep.getEnabledEventTypes();
+ assertEquals(2, enabledEventTypes.size());
+
+ // remove all event types
+ configRep.setEnabledEventTypes(Collections.EMPTY_LIST);
+ saveConfig();
+
+ // removing all event types restores default events
+ assertEquals(defaultEventCount, configRep.getEnabledEventTypes().size());
+ }
+
+ private boolean hasEventType(String eventType) {
+ for (String event : configRep.getEnabledEventTypes()) {
+ if (eventType.equals(event)) return true;
+ }
+
+ return false;
+ }
+
+ @Test
+ public void includeRepresentationTest() {
+ enableEvents();
+
+ assertTrue(configRep.isAdminEventsEnabled());
+ assertFalse(configRep.isAdminEventsDetailsEnabled());
+
+ configRep.setAdminEventsDetailsEnabled(Boolean.TRUE);
+ saveConfig();
+
+ assertTrue(configRep.isAdminEventsDetailsEnabled());
+ }
+
+ @Test
+ public void setLoginEventExpirationTest() {
+ enableEvents();
+
+ assertNull(configRep.getEventsExpiration());
+
+ Long oneHour = 3600L;
+ configRep.setEventsExpiration(oneHour);
+ saveConfig();
+
+ assertEquals(oneHour, configRep.getEventsExpiration());
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/LoginEventsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/LoginEventsTest.java
new file mode 100644
index 0000000..142bfff
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/LoginEventsTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.event;
+
+import java.util.Arrays;
+import java.util.List;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.representations.idm.EventRepresentation;
+import org.keycloak.testsuite.console.page.events.LoginEvents;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test getting and filtering login-related events.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
+ */
+public class LoginEventsTest extends AbstractEventTest {
+
+ @Page
+ private LoginEvents loginEventsPage;
+
+ @Before
+ public void init() {
+ configRep.setEventsEnabled(true);
+ saveConfig();
+ testRealmResource().clearEvents();
+ }
+
+ private List<EventRepresentation> events() {
+ return testRealmResource().getEvents();
+ }
+
+ private void badLogin() {
+ loginEventsPage.navigateTo();
+ loginPage.form().login("bad", "user");
+ }
+
+ private void pause(int seconds) {
+ try {
+ Thread.sleep(seconds * 1000L);
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void clearEventsTest() {
+ assertEquals(0, events().size());
+ badLogin();
+ badLogin();
+ assertEquals(2, events().size());
+ testRealmResource().clearEvents();
+ assertEquals(0, events().size());
+ }
+
+ @Test
+ public void loggingOfCertainTypeTest() {
+ assertEquals(0, events().size());
+ configRep.setEnabledEventTypes(Arrays.asList("REVOKE_GRANT"));
+ saveConfig();
+
+ badLogin();
+ assertEquals(0, events().size());
+
+ configRep.setEnabledEventTypes(Arrays.asList("LOGIN_ERROR"));
+ saveConfig();
+
+ badLogin();
+ assertEquals(1, events().size());
+ }
+
+ @Test
+ public void filterTest() {
+ badLogin();
+ badLogin();
+ assertEquals(2, events().size());
+
+ List<EventRepresentation> filteredEvents = testRealmResource().getEvents(Arrays.asList("REVOKE_GRANT"), null, null, null, null, null, null, null);
+ assertEquals(0, filteredEvents.size());
+
+ filteredEvents = testRealmResource().getEvents(Arrays.asList("LOGIN_ERROR"), null, null, null, null, null, null, null);
+ assertEquals(2, filteredEvents.size());
+ }
+
+ /*
+ Removed this test because it takes too long. The default interval for
+ event cleanup is 15 minutes (900 seconds). I don't have time to figure out
+ a way to set the cleanup thread to a lower interval for testing.
+ @Test
+ public void eventExpirationTest() {
+ configRep.setEventsExpiration(1L); // second
+ saveConfig();
+ badLogin();
+ assertEquals(1, events().size());
+ pause(900); // pause 900 seconds
+ assertEquals(0, events().size());
+ }**/
+
+}
diff --git a/testsuite/integration-arquillian/tests/other/adapters/wildfly/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/wildfly/pom.xml
index 26b0c48..09f71a2 100644
--- a/testsuite/integration-arquillian/tests/other/adapters/wildfly/pom.xml
+++ b/testsuite/integration-arquillian/tests/other/adapters/wildfly/pom.xml
@@ -117,6 +117,32 @@
</systemPropertyVariables>
</configuration>
</plugin>
+
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>xml-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>configure-adapter-debug-log</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <transformationSets>
+ <transformationSet>
+ <dir>${app.server.wildfly.home}/standalone/configuration</dir>
+ <includes>
+ <include>standalone.xml</include>
+ </includes>
+ <stylesheet>src/main/xslt/add-adapter-log-level.xsl</stylesheet>
+ <outputDir>${app.server.wildfly.home}/standalone/configuration</outputDir>
+ </transformationSet>
+ </transformationSets>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
diff --git a/testsuite/integration-arquillian/tests/other/adapters/wildfly/src/main/xslt/add-adapter-log-level.xsl b/testsuite/integration-arquillian/tests/other/adapters/wildfly/src/main/xslt/add-adapter-log-level.xsl
new file mode 100644
index 0000000..39cec8b
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/other/adapters/wildfly/src/main/xslt/add-adapter-log-level.xsl
@@ -0,0 +1,50 @@
+<!--
+ ~ 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.
+ -->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:xalan="http://xml.apache.org/xalan"
+ xmlns:j="urn:jboss:domain:4.0"
+ xmlns:ds="urn:jboss:domain:datasources:4.0"
+ xmlns:k="urn:jboss:domain:keycloak:1.1"
+ xmlns:sec="urn:jboss:domain:security:1.2"
+ version="2.0"
+ exclude-result-prefixes="xalan j ds k sec">
+
+ <xsl:param name="config"/>
+
+ <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" xalan:indent-amount="4" standalone="no"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:variable name="nsDS" select="'urn:jboss:domain:logging:'"/>
+
+ <xsl:template match="//*[local-name()='subsystem' and starts-with(namespace-uri(), $nsDS)]
+ /*[local-name()='root-logger' and starts-with(namespace-uri(), $nsDS)]">
+ <logger category="org.keycloak.adapters">
+ <level name="DEBUG"/>
+ </logger>
+ <xsl:copy>
+ <xsl:apply-templates select="@* | node()" />
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()" />
+ </xsl:copy>
+ </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file