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 0000000000..f615cbd032 --- /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 0000000000..746aa718de --- /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 0000000000..93be310efb --- /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 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 getDetails() { + return details; + } + + public void setDetails(Map 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 0242d24a98..87f6763c1f 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 getEvents(); + + @Path("events") + @GET + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public List getEvents(@QueryParam("type") List 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 getAdminEvents(); + + @GET + @Path("admin-events") + @Produces(MediaType.APPLICATION_JSON) + List getAdminEvents(@QueryParam("operationTypes") List 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 868aa10c4c..30f7d5f1ae 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 22eb5db033..15b0e8117b 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 getEvents(@QueryParam("client") String client, + public List getEvents(@QueryParam("type") List 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 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 toEventListRep(List events) { + List 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 getEvents(@QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient, + public List getEvents(@QueryParam("operationTypes") List 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 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 toAdminEventRep(List events) { + List 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/event/AbstractEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AbstractEventTest.java new file mode 100644 index 0000000000..3bf0d19ff9 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/AbstractEventTest.java @@ -0,0 +1,55 @@ +/* + * 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 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); + 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 0000000000..bdf699a1d5 --- /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 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 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 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 0000000000..a2bf746537 --- /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 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 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 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 0000000000..d23c57aedb --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/event/LoginEventsTest.java @@ -0,0 +1,120 @@ +/* + * 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.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); + configRep.setEnabledEventTypes(Collections.EMPTY_LIST); // resets to all types + saveConfig(); + testRealmResource().clearEvents(); + } + + private List 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 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()); + }**/ + +}