diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminEvents.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminEvents.java index b452866a9da..831d6086013 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminEvents.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectAdminEvents.java @@ -8,4 +8,9 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectAdminEvents { + + String ref() default ""; + + String realmRef() default ""; + } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectEvents.java b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectEvents.java index ef387bdb8e7..962d9730398 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectEvents.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/annotations/InjectEvents.java @@ -8,4 +8,9 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectEvents { + + String ref() default ""; + + String realmRef() default ""; + } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEvents.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEvents.java new file mode 100644 index 00000000000..95d91ce6a81 --- /dev/null +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEvents.java @@ -0,0 +1,102 @@ +package org.keycloak.testframework.events; + +import org.jboss.logging.Logger; +import org.keycloak.common.util.Time; +import org.keycloak.testframework.realm.ManagedRealm; + +import java.text.SimpleDateFormat; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public abstract class AbstractEvents { + + protected final ManagedRealm realm; + protected final LinkedList events = new LinkedList<>(); + protected final Set processedEvents = new HashSet<>(); + + protected long testStarted; + protected long timeOffset; + protected long lastFetch; + + public AbstractEvents(ManagedRealm realm) { + this.realm = realm; + } + + public E poll() { + long currentTimeOffset = getCurrentTimeOffset(); + if (timeOffset != currentTimeOffset) { + getLogger().debugv("Timeoffset changed to {0}, resetting events", timeOffset); + + events.clear(); + timeOffset = currentTimeOffset; + lastFetch = -1; + } + + if (events.isEmpty()) { + long from = lastFetch != -1 ? lastFetch : testStarted + currentTimeOffset; + long to = getCurrentTime() + currentTimeOffset; + + Logger logger = getLogger(); + if (logger.isDebugEnabled()) { + getLogger().debugv("Fetching events from server between {0} and {1}" + (timeOffset != 0 ? "; current timeoffset is {2}" : ""), formatDate(from), formatDate(to), timeOffset); + } + + getEvents(from, to) + .stream().filter(e -> !processedEvents.contains(getId(e))) + .forEach(e -> { + processedEvents.add(getId(e)); + this.events.add(e); + }); + + lastFetch = to; + } + + R rep = events.poll(); + return rep != null ? convert(rep) : null; + } + + public void clear() { + events.clear(); + clearServerEvents(); + } + + void testStarted() { + testStarted = getCurrentTime(); + timeOffset = getCurrentTimeOffset(); + lastFetch = -1; + } + + protected abstract List getEvents(long from, long to); + + protected String getRealmName(String realmId) { + if (realm.getId().equals(realmId)) { + return realm.getName(); + } else { + return null; + } + } + + protected abstract String getId(R representation); + + protected abstract E convert(R representation); + + protected abstract void clearServerEvents(); + + protected abstract Logger getLogger(); + + protected long getCurrentTime() { + return System.currentTimeMillis(); + } + + protected long getCurrentTimeOffset() { + return TimeUnit.MILLISECONDS.convert(Time.getOffset(), TimeUnit.SECONDS); + } + + protected String formatDate(long timestamp) { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS").format(timestamp); + } + +} diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEventsSupplier.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEventsSupplier.java new file mode 100644 index 00000000000..c435df8da3d --- /dev/null +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/AbstractEventsSupplier.java @@ -0,0 +1,45 @@ +package org.keycloak.testframework.events; + +import org.keycloak.testframework.injection.InstanceContext; +import org.keycloak.testframework.injection.LifeCycle; +import org.keycloak.testframework.injection.RequestedInstance; +import org.keycloak.testframework.injection.Supplier; +import org.keycloak.testframework.injection.SupplierHelpers; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.RealmConfigInterceptor; + +import java.lang.annotation.Annotation; + +@SuppressWarnings("rawtypes") +public abstract class AbstractEventsSupplier implements Supplier, RealmConfigInterceptor { + + @Override + public E getValue(InstanceContext instanceContext) { + String realmRef = SupplierHelpers.getAnnotationField(instanceContext.getAnnotation(), "realmRef"); + ManagedRealm realm = instanceContext.getDependency(ManagedRealm.class, realmRef); + return createValue(realm); + } + + @Override + public LifeCycle getDefaultLifecycle() { + return LifeCycle.GLOBAL; + } + + @Override + public boolean compatible(InstanceContext a, RequestedInstance b) { + return true; + } + + @Override + public void onBeforeEach(InstanceContext instanceContext) { + instanceContext.getValue().testStarted(); + } + + @Override + public void close(InstanceContext instanceContext) { + instanceContext.getValue().clear(); + } + + protected abstract E createValue(ManagedRealm realm); + +} diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEvents.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEvents.java index 43dc788d8ea..bd100607038 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEvents.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEvents.java @@ -1,32 +1,68 @@ package org.keycloak.testframework.events; +import org.jboss.logging.Logger; import org.keycloak.events.admin.AdminEvent; +import org.keycloak.events.admin.AuthDetails; +import org.keycloak.events.admin.OperationType; +import org.keycloak.events.admin.ResourceType; +import org.keycloak.representations.idm.AdminEventRepresentation; +import org.keycloak.representations.idm.AuthDetailsRepresentation; +import org.keycloak.testframework.realm.ManagedRealm; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; +import java.util.List; -public class AdminEvents implements SysLogListener{ +public class AdminEvents extends AbstractEvents { - private final BlockingQueue adminEvents = new LinkedBlockingQueue<>(); + private static final Logger LOGGER = Logger.getLogger(AdminEvents.class); - public AdminEvent poll() { - try { - return adminEvents.poll(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - return null; - } - } - - public void clear() { - adminEvents.clear(); + public AdminEvents(ManagedRealm realm) { + super(realm); } @Override - public void onLog(SysLog sysLog) { - AdminEvent adminEvent = AdminEventsParser.parse(sysLog); - if (adminEvent != null) { - adminEvents.add(adminEvent); - } + protected List getEvents(long from, long to) { + return realm.admin().getAdminEvents(null, null, null, null, null, null, null, from, to, null, null, "asc"); + } + + @Override + protected String getId(AdminEventRepresentation rep) { + return rep.getId(); + } + + @Override + protected AdminEvent convert(AdminEventRepresentation rep) { + AdminEvent e = new AdminEvent(); + e.setId(rep.getId()); + e.setTime(rep.getTime()); + e.setRealmId(rep.getRealmId()); + e.setRealmName(getRealmName(rep.getRealmId())); + e.setAuthDetails(convert(rep.getAuthDetails())); + e.setResourceType(ResourceType.valueOf(rep.getResourceType())); + e.setOperationType(OperationType.valueOf(rep.getOperationType())); + e.setResourcePath(rep.getResourcePath()); + e.setRepresentation(rep.getRepresentation()); + e.setError(rep.getError()); + e.setDetails(rep.getDetails()); + return e; + } + + private AuthDetails convert(AuthDetailsRepresentation rep) { + AuthDetails d = new AuthDetails(); + d.setClientId(rep.getClientId()); + d.setIpAddress(rep.getIpAddress()); + d.setRealmId(rep.getRealmId()); + d.setRealmName(getRealmName(rep.getRealmId())); + d.setUserId(rep.getUserId()); + return d; + } + + @Override + protected void clearServerEvents() { + realm.admin().clearAdminEvents(); + } + + @Override + protected Logger getLogger() { + return LOGGER; } } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEventsSupplier.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEventsSupplier.java index 44aebcca66a..a23ca546abc 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEventsSupplier.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/AdminEventsSupplier.java @@ -2,12 +2,10 @@ package org.keycloak.testframework.events; import org.keycloak.testframework.annotations.InjectAdminEvents; import org.keycloak.testframework.injection.InstanceContext; -import org.keycloak.testframework.injection.LifeCycle; -import org.keycloak.testframework.injection.RequestedInstance; -import org.keycloak.testframework.injection.Supplier; -import org.keycloak.testframework.injection.SupplierOrder; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.RealmConfigBuilder; -public class AdminEventsSupplier implements Supplier { +public class AdminEventsSupplier extends AbstractEventsSupplier { @Override public Class getAnnotationClass() { @@ -20,35 +18,13 @@ public class AdminEventsSupplier implements Supplier instanceContext) { - AdminEvents adminEvents = new AdminEvents(); - SysLogServer sysLogServer = instanceContext.getDependency(SysLogServer.class); - instanceContext.addNote("server", sysLogServer); - return adminEvents; + public AdminEvents createValue(ManagedRealm realm) { + return new AdminEvents(realm); } @Override - public void onBeforeEach(InstanceContext instanceContext) { - instanceContext.getNote("server", SysLogServer.class).addListener(instanceContext.getValue()); + public RealmConfigBuilder intercept(RealmConfigBuilder realm, InstanceContext instanceContext) { + return realm.adminEventsEnabled(true); } - @Override - public LifeCycle getDefaultLifecycle() { - return LifeCycle.METHOD; - } - - @Override - public void close(InstanceContext instanceContext) { - instanceContext.getNote("server", SysLogServer.class).removeListener(instanceContext.getValue()); - } - - @Override - public boolean compatible(InstanceContext a, RequestedInstance b) { - return true; - } - - @Override - public int order() { - return SupplierOrder.BEFORE_KEYCLOAK_SERVER; - } } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/Events.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/Events.java index 064936d3a16..769798e3084 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/Events.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/Events.java @@ -1,32 +1,56 @@ package org.keycloak.testframework.events; +import org.jboss.logging.Logger; import org.keycloak.events.Event; +import org.keycloak.events.EventType; +import org.keycloak.representations.idm.EventRepresentation; +import org.keycloak.testframework.realm.ManagedRealm; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; +import java.util.List; -public class Events implements SysLogListener { +public class Events extends AbstractEvents { - private final BlockingQueue events = new LinkedBlockingQueue<>(); + private static final Logger LOGGER = Logger.getLogger(Events.class); - public Event poll() { - try { - return events.poll(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - return null; - } - } - - public void clear() { - events.clear(); + public Events(ManagedRealm realm) { + super(realm); } @Override - public void onLog(SysLog sysLog) { - Event event = EventParser.parse(sysLog); - if (event != null) { - events.add(event); - } + protected List getEvents(long from, long to) { + return realm.admin().getEvents(null, null, null, from, to, null, null, null, "asc"); } + + @Override + protected String getId(EventRepresentation rep) { + return rep.getId(); + } + + @Override + protected Event convert(EventRepresentation rep) { + Event e = new Event(); + e.setId(rep.getId()); + e.setTime(rep.getTime()); + e.setType(EventType.valueOf(rep.getType())); + e.setRealmId(rep.getRealmId()); + e.setRealmName(getRealmName(rep.getRealmId())); + e.setClientId(rep.getClientId()); + e.setUserId(rep.getUserId()); + e.setSessionId(rep.getSessionId()); + e.setIpAddress(rep.getIpAddress()); + e.setError(rep.getError()); + e.setDetails(rep.getDetails()); + return e; + } + + @Override + protected void clearServerEvents() { + realm.admin().clearEvents(); + } + + @Override + protected Logger getLogger() { + return LOGGER; + } + } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/events/EventsSupplier.java b/test-framework/core/src/main/java/org/keycloak/testframework/events/EventsSupplier.java index be0ff305748..a1b4605b23f 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/events/EventsSupplier.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/events/EventsSupplier.java @@ -2,12 +2,10 @@ package org.keycloak.testframework.events; import org.keycloak.testframework.annotations.InjectEvents; import org.keycloak.testframework.injection.InstanceContext; -import org.keycloak.testframework.injection.LifeCycle; -import org.keycloak.testframework.injection.RequestedInstance; -import org.keycloak.testframework.injection.Supplier; -import org.keycloak.testframework.injection.SupplierOrder; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.realm.RealmConfigBuilder; -public class EventsSupplier implements Supplier { +public class EventsSupplier extends AbstractEventsSupplier { @Override public Class getAnnotationClass() { @@ -20,35 +18,13 @@ public class EventsSupplier implements Supplier { } @Override - public Events getValue(InstanceContext instanceContext) { - Events events = new Events(); - SysLogServer sysLogServer = instanceContext.getDependency(SysLogServer.class); - instanceContext.addNote("server", sysLogServer); - return events; + protected Events createValue(ManagedRealm realm) { + return new Events(realm); } @Override - public void onBeforeEach(InstanceContext instanceContext) { - instanceContext.getNote("server", SysLogServer.class).addListener(instanceContext.getValue()); + public RealmConfigBuilder intercept(RealmConfigBuilder realm, InstanceContext instanceContext) { + return realm.eventsEnabled(true); } - @Override - public LifeCycle getDefaultLifecycle() { - return LifeCycle.METHOD; - } - - @Override - public void close(InstanceContext instanceContext) { - instanceContext.getNote("server", SysLogServer.class).removeListener(instanceContext.getValue()); - } - - @Override - public boolean compatible(InstanceContext a, RequestedInstance b) { - return true; - } - - @Override - public int order() { - return SupplierOrder.BEFORE_KEYCLOAK_SERVER; - } } diff --git a/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java b/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java index 98baead838e..bbb3e46903d 100644 --- a/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java +++ b/test-framework/core/src/main/java/org/keycloak/testframework/realm/RealmConfigBuilder.java @@ -72,6 +72,16 @@ public class RealmConfigBuilder { return this; } + public RealmConfigBuilder eventsEnabled(boolean enabled) { + rep.setEventsEnabled(enabled); + return this; + } + + public RealmConfigBuilder adminEventsEnabled(boolean enabled) { + rep.setAdminEventsEnabled(enabled); + return this; + } + public RealmConfigBuilder eventsListeners(String... eventListeners) { if (rep.getEventsListeners() == null) { rep.setEventsListeners(new LinkedList<>()); diff --git a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/AdminEventsTest.java b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/AdminEventsTest.java index 44d7e05e703..caf1bb5dc3a 100644 --- a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/AdminEventsTest.java +++ b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/AdminEventsTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.keycloak.admin.client.Keycloak; import org.keycloak.events.admin.OperationType; +import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testframework.annotations.InjectAdminClient; @@ -13,21 +14,28 @@ import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.testframework.events.AdminEvents; import org.keycloak.testframework.realm.ManagedRealm; +import java.util.List; + @KeycloakIntegrationTest public class AdminEventsTest { - @InjectAdminEvents - private AdminEvents adminEvents; - - @InjectAdminClient - private Keycloak adminClient; - @InjectRealm private ManagedRealm realm; + @InjectAdminEvents + private AdminEvents adminEvents; + + @InjectRealm(ref = "master", attachTo = "master") + private ManagedRealm master; + + @InjectAdminEvents(ref = "master", realmRef = "master") + private AdminEvents masterAdminEvents; + + @InjectAdminClient + private Keycloak adminClient; + @Test public void testAdminEventOnUserCreateAndDelete() { - String userName = "create_user"; UserRepresentation userRep = new UserRepresentation(); @@ -47,21 +55,23 @@ public class AdminEventsTest { @Test public void testAdminEventOnRealmCreateAndUpdate() { + master.updateWithCleanup(r -> r.adminEventsEnabled(true)); String realmName = "create_realm"; RealmRepresentation realmRep = new RealmRepresentation(); realmRep.setRealm(realmName); realmRep.setEnabled(true); + realmRep.setAdminEventsEnabled(true); adminClient.realms().create(realmRep); - Assertions.assertEquals(OperationType.CREATE, adminEvents.poll().getOperationType()); + Assertions.assertEquals(OperationType.CREATE, masterAdminEvents.poll().getOperationType()); + } - RealmRepresentation realmRep2 = adminClient.realms().realm(realmName).toRepresentation(); - realmRep2.setEnabled(false); - - adminClient.realms().realm(realmName).update(realmRep2); + @Test + public void testAdminEventOnRealmUpdate() { + realm.updateWithCleanup(r -> r.editUsernameAllowed(true)); Assertions.assertEquals(OperationType.UPDATE, adminEvents.poll().getOperationType()); } diff --git a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/EventsTest.java b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/EventsTest.java index c604eb3fc55..ed07c30441a 100644 --- a/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/EventsTest.java +++ b/test-framework/examples/tests/src/test/java/org/keycloak/test/examples/EventsTest.java @@ -3,43 +3,57 @@ package org.keycloak.test.examples; import com.nimbusds.oauth2.sdk.GeneralException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.keycloak.events.Event; import org.keycloak.events.EventType; import org.keycloak.testframework.annotations.InjectEvents; +import org.keycloak.testframework.annotations.InjectRealm; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.testframework.events.Events; import org.keycloak.testframework.oauth.nimbus.OAuthClient; import org.keycloak.testframework.oauth.nimbus.annotations.InjectOAuthClient; -import org.keycloak.testframework.ui.annotations.InjectPage; -import org.keycloak.testframework.ui.annotations.InjectWebDriver; -import org.keycloak.testframework.ui.page.LoginPage; -import org.openqa.selenium.WebDriver; +import org.keycloak.testframework.realm.ManagedRealm; +import org.keycloak.testframework.remote.timeoffset.InjectTimeOffSet; +import org.keycloak.testframework.remote.timeoffset.TimeOffSet; import java.io.IOException; -import java.net.URL; @KeycloakIntegrationTest public class EventsTest { + @InjectRealm + private ManagedRealm realm; + @InjectEvents private Events events; @InjectOAuthClient private OAuthClient oAuthClient; - @InjectWebDriver - private WebDriver webDriver; - - @InjectPage - private LoginPage loginPage; + @InjectTimeOffSet + TimeOffSet timeOffSet; @Test - public void testFailedLogin() throws GeneralException, IOException { - URL authorizationRequestURL = oAuthClient.authorizationRequest(); - webDriver.navigate().to(authorizationRequestURL); - loginPage.fillLogin("invalid", "invalid"); - loginPage.submit(); + public void testFailedLogin() { + oAuthClient.resourceOwnerCredentialGrant("invalid", "invalid"); - Assertions.assertEquals(EventType.LOGIN_ERROR, events.poll().getType()); + Event event = events.poll(); + Assertions.assertEquals(EventType.LOGIN_ERROR, event.getType()); + Assertions.assertEquals("invalid", event.getDetails().get("username")); + + oAuthClient.resourceOwnerCredentialGrant("invalid2", "invalid"); + + event = events.poll(); + Assertions.assertEquals(EventType.LOGIN_ERROR, event.getType()); + Assertions.assertEquals("invalid2", event.getDetails().get("username")); + } + + @Test + public void testTimeOffset() throws GeneralException, IOException { + timeOffSet.set(60); + + oAuthClient.clientCredentialGrant(); + + Assertions.assertEquals(EventType.CLIENT_LOGIN, events.poll().getType()); } @Test