Extend REST API for login and admin events to support sync scenarios (#36601)

Closes #36600

Signed-off-by: stianst <stianst@gmail.com>
This commit is contained in:
Stian Thorgersen
2025-01-20 14:32:55 +01:00
committed by GitHub
parent 87c79808dd
commit fc2b9018f1
14 changed files with 295 additions and 63 deletions

View File

@@ -24,6 +24,7 @@ import java.util.Map;
*/
public class AdminEventRepresentation {
private String id;
private long time;
private String realmId;
private AuthDetailsRepresentation authDetails;
@@ -34,6 +35,14 @@ public class AdminEventRepresentation {
private String error;
private Map<String, String> details;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public long getTime() {
return time;
}

View File

@@ -24,6 +24,7 @@ import java.util.Map;
*/
public class EventRepresentation {
private String id;
private long time;
private String type;
private String realmId;
@@ -34,6 +35,14 @@ public class EventRepresentation {
private String error;
private Map<String, String> details;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public long getTime() {
return time;
}
@@ -128,6 +137,7 @@ public class EventRepresentation {
@Override
public int hashCode() {
int result = (int) (time ^ (time >>> 32));
result = 31 * result + (id != null ? id.hashCode() : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (realmId != null ? realmId.hashCode() : 0);
result = 31 * result + (clientId != null ? clientId.hashCode() : 0);

View File

@@ -13,6 +13,9 @@ include::topics/templates/document-attributes.adoc[]
:release_header_latest_link: {releasenotes_link_latest}
include::topics/templates/release-header.adoc[]
== {project_name_full} 26.2.0
include::topics/26_2_0.adoc[leveloffset=2]
== {project_name_full} 26.1.0
include::topics/26_1_0.adoc[leveloffset=2]

View File

@@ -0,0 +1,10 @@
= Additional query parameters in Admin Events API
The Admin Events API now supports filtering for events based on Epoc timestamps in addition to the previous
`yyyy-MM-dd` format. This provides more fine-grained control of the window of events to retrieve.
A `direction` query parameter was also added, allowing controlling the order of returned items as `asc` or
`desc`. In the past the events where always returned in `desc` order (most recent events first).
Finally, the returned event representations now also include the `id`, which provides a unique identifier for
an event.

View File

@@ -126,6 +126,24 @@ public interface RealmResource {
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults);
@Path("events")
@GET
@Produces(MediaType.APPLICATION_JSON)
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,
@QueryParam("direction") String direction);
@Path("events")
@GET
@Produces(MediaType.APPLICATION_JSON)
List<EventRepresentation> getEvents(@QueryParam("type") List<String> types, @QueryParam("client") String client,
@QueryParam("user") String user, @QueryParam("dateFrom") long dateFrom, @QueryParam("dateTo") long dateTo,
@QueryParam("ipAddress") String ipAddress, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults,
@QueryParam("direction") String direction);
@DELETE
@Path("admin-events")
void clearAdminEvents();
@@ -153,6 +171,24 @@ public interface RealmResource {
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults);
@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("resourceTypes") List<String> resourceTypes, @QueryParam("dateFrom") String dateFrom,
@QueryParam("dateTo") String dateTo, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults, @QueryParam("direction") String direction);
@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("resourceTypes") List<String> resourceTypes, @QueryParam("dateFrom") long dateFrom,
@QueryParam("dateTo") long dateTo, @QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResults, @QueryParam("direction") String direction);
@GET
@Path("events/config")
@Produces(MediaType.APPLICATION_JSON)

View File

@@ -121,14 +121,26 @@ public class JpaAdminEventQuery implements AdminEventQuery {
}
@Override
@Deprecated
public AdminEventQuery fromTime(Date fromTime) {
predicates.add(cb.greaterThanOrEqualTo(root.<Long>get("time"), fromTime.getTime()));
return fromTime(fromTime.getTime());
}
@Override
public AdminEventQuery fromTime(long fromTime) {
predicates.add(cb.greaterThanOrEqualTo(root.get("time"), fromTime));
return this;
}
@Override
@Deprecated
public AdminEventQuery toTime(Date toTime) {
predicates.add(cb.lessThanOrEqualTo(root.<Long>get("time"), toTime.getTime()));
return toTime(toTime.getTime());
}
@Override
public AdminEventQuery toTime(long toTime) {
predicates.add(cb.lessThanOrEqualTo(root.get("time"), toTime));
return this;
}

View File

@@ -90,8 +90,14 @@ public class JpaEventQuery implements EventQuery {
}
@Override
@Deprecated
public EventQuery fromDate(Date fromDate) {
predicates.add(cb.greaterThanOrEqualTo(root.<Long>get("time"), fromDate.getTime()));
return fromDate(fromDate.getTime());
}
@Override
public EventQuery fromDate(long fromDate) {
predicates.add(cb.greaterThanOrEqualTo(root.get("time"), fromDate));
return this;
}
@@ -103,7 +109,12 @@ public class JpaEventQuery implements EventQuery {
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
calendar.set(Calendar.MILLISECOND, 999);
predicates.add(cb.lessThanOrEqualTo(root.<Long>get("time"), calendar.getTimeInMillis()));
return toDate(calendar.getTimeInMillis());
}
@Override
public EventQuery toDate(long toDate) {
predicates.add(cb.lessThanOrEqualTo(root.get("time"), toDate));
return this;
}

View File

@@ -56,19 +56,35 @@ public interface EventQuery {
EventQuery user(String userId);
/**
* Search events that are newer than {@code fromDate}
* Search events that are on or after {@code fromDate}
* @param fromDate date
* @return this object for method chaining
*/
@Deprecated
EventQuery fromDate(Date fromDate);
/**
* Search events that are older than {@code toDate}
* Search events that are on or after {@code fromDate}
* @param fromDate from timestamp
* @return this object for method chaining
*/
EventQuery fromDate(long fromDate);
/**
* Search events that are on or before {@code toDate}
* @param toDate date
* @return this object for method chaining
*/
@Deprecated
EventQuery toDate(Date toDate);
/**
* Search events that are on or before {@code toDate}
* @param toDate to timestamp
* @return this object for method chaining
*/
EventQuery toDate(long toDate);
/**
* Search events from ipAddress
* @param ipAddress ip

View File

@@ -18,8 +18,6 @@
package org.keycloak.events.admin;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -96,21 +94,39 @@ public interface AdminEventQuery {
AdminEventQuery resourcePath(String resourcePath);
/**
* Search by events after the specified time
* Search by events on or after the specified time
*
* @param fromTime from date
* @return <code>this</code> for method chaining
*/
@Deprecated
AdminEventQuery fromTime(Date fromTime);
/**
* Search by events before the specified time
* Search by events on or after the specified timestamp
*
* @param fromTime from timestamp
* @return <code>this</code> for method chaining
*/
AdminEventQuery fromTime(long fromTime);
/**
* Search by events on or before the specified time
*
* @param toTime to date
* @return <code>this</code> for method chaining
*/
@Deprecated
AdminEventQuery toTime(Date toTime);
/**
* Search by events on or before the specified timestamp
*
* @param toTime to timestamp
* @return <code>this</code> for method chaining
*/
AdminEventQuery toTime(long toTime);
/**
* Used for pagination
*

View File

@@ -288,6 +288,7 @@ public class ModelToRepresentation {
public static EventRepresentation toRepresentation(Event event) {
EventRepresentation rep = new EventRepresentation();
rep.setId(event.getId());
rep.setTime(event.getTime());
rep.setType(event.getType().toString());
rep.setRealmId(event.getRealmId());
@@ -302,6 +303,7 @@ public class ModelToRepresentation {
public static AdminEventRepresentation toRepresentation(AdminEvent adminEvent) {
AdminEventRepresentation rep = new AdminEventRepresentation();
rep.setId(adminEvent.getId());
rep.setTime(adminEvent.getTime());
rep.setRealmId(adminEvent.getRealmId());
if (adminEvent.getAuthDetails() != null) {

View File

@@ -16,24 +16,11 @@
*/
package org.keycloak.services.resources.admin;
import static org.keycloak.util.JsonSerialization.readValue;
import java.io.InputStream;
import java.security.cert.X509Certificate;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.NotFoundException;
@@ -48,9 +35,6 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import jakarta.ws.rs.core.StreamingOutput;
import com.fasterxml.jackson.core.type.TypeReference;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
@@ -114,6 +98,7 @@ import org.keycloak.services.resources.admin.ext.AdminRealmResourceProvider;
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
import org.keycloak.services.resources.admin.permissions.AdminPermissionManagement;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.services.util.DateUtil;
import org.keycloak.storage.DatastoreProvider;
import org.keycloak.storage.ExportImportManager;
import org.keycloak.storage.StoreSyncEvent;
@@ -121,6 +106,17 @@ import org.keycloak.utils.GroupUtils;
import org.keycloak.utils.ProfileHelper;
import org.keycloak.utils.ReservedCharValidator;
import java.io.InputStream;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.keycloak.util.JsonSerialization.readValue;
/**
* Base resource class for the admin REST api of one realm
*
@@ -131,6 +127,7 @@ import org.keycloak.utils.ReservedCharValidator;
@Extension(name = KeycloakOpenAPI.Profiles.ADMIN, value = "")
public class RealmAdminResource {
protected static final Logger logger = Logger.getLogger(RealmAdminResource.class);
protected final AdminPermissionEvaluator auth;
protected final RealmModel realm;
private final AdminEventBuilder adminEvent;
@@ -786,11 +783,12 @@ public class RealmAdminResource {
public Stream<EventRepresentation> getEvents(@Parameter(description = "The types of events to return") @QueryParam("type") List<String> types,
@Parameter(description = "App or oauth client name") @QueryParam("client") String client,
@Parameter(description = "User id") @QueryParam("user") String user,
@Parameter(description = "From date") @QueryParam("dateFrom") String dateFrom,
@Parameter(description = "To date") @QueryParam("dateTo") String dateTo,
@Parameter(description = "From (inclusive) date (yyyy-MM-dd) or time in Epoch timestamp") @QueryParam("dateFrom") String dateFrom,
@Parameter(description = "To (inclusive) date (yyyy-MM-dd) or time in Epoch timestamp") @QueryParam("dateTo") String dateTo,
@Parameter(description = "IP Address") @QueryParam("ipAddress") String ipAddress,
@Parameter(description = "Paging offset") @QueryParam("first") Integer firstResult,
@Parameter(description = "Maximum results size (defaults to 100)") @QueryParam("max") Integer maxResults) {
@Parameter(description = "Maximum results size (defaults to 100)") @QueryParam("max") Integer maxResults,
@Parameter(description = "The direction to sort events by (asc or desc)") @QueryParam("direction") String direction) {
auth.realm().requireViewEvents();
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
@@ -813,30 +811,35 @@ public class RealmAdminResource {
}
if(dateFrom != null) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date from = null;
try {
from = df.parse(dateFrom);
} catch (ParseException e) {
throw new BadRequestException("Invalid value for 'Date(From)', expected format is yyyy-MM-dd");
query.fromDate(DateUtil.toStartOfDay(dateFrom));
} catch (Throwable t) {
throw new BadRequestException("Invalid value for 'dateFrom', expected format is yyyy-MM-dd or an Epoch timestamp");
}
query.fromDate(from);
}
if(dateTo != null) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date to = null;
try {
to = df.parse(dateTo);
} catch (ParseException e) {
throw new BadRequestException("Invalid value for 'Date(To)', expected format is yyyy-MM-dd");
query.toDate(DateUtil.toEndOfDay(dateTo));
} catch (Throwable t) {
throw new BadRequestException("Invalid value for 'dateTo', expected format is yyyy-MM-dd or an Epoch timestamp");
}
query.toDate(to);
}
if (ipAddress != null) {
query.ipAddress(ipAddress);
}
if (direction != null) {
if ("asc".equals(direction)) {
query.orderByAscTime();
} else if ("desc".equals(direction)) {
query.orderByDescTime();
} else {
throw new BadRequestException("Invalid value for sortDirection, expected value is asc or desc");
}
}
if (firstResult != null) {
query.firstResult(firstResult);
}
@@ -874,10 +877,13 @@ public class RealmAdminResource {
@Operation( summary = "Get admin events Returns all admin events, or filters events based on URL query parameters listed here")
public Stream<AdminEventRepresentation> getEvents(@QueryParam("operationTypes") List<String> operationTypes, @QueryParam("authRealm") String authRealm, @QueryParam("authClient") String authClient,
@Parameter(description = "user id") @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("resourcePath") String resourcePath,
@Parameter(description = "From (inclusive) date (yyyy-MM-dd) or time in Epoch timestamp") @QueryParam("dateFrom") String dateFrom,
@Parameter(description = "To (inclusive) date (yyyy-MM-dd) or time in Epoch timestamp") @QueryParam("dateTo") String dateTo,
@QueryParam("first") Integer firstResult,
@Parameter(description = "Maximum results size (defaults to 100)") @QueryParam("max") Integer maxResults,
@QueryParam("resourceTypes") List<String> resourceTypes) {
@QueryParam("resourceTypes") List<String> resourceTypes,
@Parameter(description = "The direction to sort events by (asc or desc)") @QueryParam("direction") String direction) {
auth.realm().requireViewEvents();
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
@@ -919,28 +925,30 @@ public class RealmAdminResource {
query.resourceType(t);
}
if(dateFrom != null) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date from = null;
try {
from = df.parse(dateFrom);
} catch (ParseException e) {
throw new BadRequestException("Invalid value for 'Date(From)', expected format is yyyy-MM-dd");
query.fromTime(DateUtil.toStartOfDay(dateFrom));
} catch (Throwable t) {
throw new BadRequestException("Invalid value for 'dateFrom', expected format is yyyy-MM-dd or an Epoch timestamp");
}
query.fromTime(from);
}
if(dateTo != null) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
Date to = null;
try {
to = df.parse(dateTo);
} catch (ParseException e) {
throw new BadRequestException("Invalid value for 'Date(To)', expected format is yyyy-MM-dd");
query.toTime(DateUtil.toEndOfDay(dateTo));
} catch (Throwable t) {
throw new BadRequestException("Invalid value for 'dateTo', expected format is yyyy-MM-dd or an Epoch timestamp");
}
}
if (direction != null) {
if ("asc".equals(direction)) {
query.orderByAscTime();
} else if ("desc".equals(direction)) {
query.orderByDescTime();
} else {
throw new BadRequestException("Invalid value for sortDirection, expected value is asc or desc");
}
query.toTime(to);
}
if (firstResult != null) {

View File

@@ -0,0 +1,39 @@
package org.keycloak.services.util;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneOffset;
public class DateUtil {
/**
* Parses a string timestamp or date to an Epoc timestamp; if the date is a ISO-8601 extended local date format,
* the time at the beginning of the day is returned.
*
* @param date the date in ISO-8601 extended local date format
* @return Epoch time for the start of the day
*/
public static long toStartOfDay(String date) {
if (date.indexOf('-') != -1) {
return LocalDate.parse(date).atStartOfDay().toEpochSecond(ZoneOffset.UTC);
} else {
return Long.parseLong(date);
}
}
/**
* Parses a string timestamp or date to an Epoc timestamp; if the date is a ISO-8601 extended local date format,
* the time at the end of the day is returned.
*
* @param date the date in ISO-8601 extended local date format
* @return Epoch time for the start of the day
*/
public static long toEndOfDay(String date) {
if (date.indexOf('-') != -1) {
return LocalDate.parse(date).atTime(LocalTime.MAX).toEpochSecond(ZoneOffset.UTC);
} else {
return Long.parseLong(date);
}
}
}

View File

@@ -18,6 +18,7 @@
package org.keycloak.testsuite.admin.event;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
@@ -32,6 +33,7 @@ import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.UserBuilder;
import org.keycloak.util.JsonSerialization;
@@ -135,6 +137,7 @@ public class AdminEventTest extends AbstractEventTest {
assertThat(events().size(), is(equalTo(4)));
AdminEventRepresentation event = events.get(0);
assertThat(event.getId(), AssertEvents.isUUID());
assertThat(event.getRealmId(), is(equalTo(realmName())));
assertThat(event.getOperationType(), is(equalTo("CREATE")));
assertThat(event.getResourceType(), is(equalTo(ResourceType.ORGANIZATION_MEMBERSHIP.name())));
@@ -233,12 +236,39 @@ public class AdminEventTest extends AbstractEventTest {
testingClient.testing("test").onAdminEvent(firstEvent, false);
testingClient.testing("test").onAdminEvent(secondEvent, false);
List<AdminEventRepresentation> adminEvents = realm.getAdminEvents(null, null, null, null, null, null, null, null, null, null);
List<AdminEventRepresentation> adminEvents = realm.getAdminEvents(null, null, null, null, null, null, null, null, null, null, null, "desc");
assertThat(adminEvents.size(), is(equalTo(2)));
assertThat(adminEvents.get(0).getOperationType(), is(equalTo(OperationType.DELETE.toString())));
assertThat(adminEvents.get(1).getOperationType(), is(equalTo(OperationType.CREATE.toString())));
adminEvents = realm.getAdminEvents(null, null, null, null, null, null, null, null, null, null, null, "asc");
assertThat(adminEvents.size(), is(equalTo(2)));
assertThat(adminEvents.get(1).getOperationType(), is(equalTo(OperationType.DELETE.toString())));
assertThat(adminEvents.get(0).getOperationType(), is(equalTo(OperationType.CREATE.toString())));
}
@Test
public void filterByEpochTimeStamp() {
RealmResource realm = adminClient.realms().realm("test");
AdminEventRepresentation event = new AdminEventRepresentation();
event.setOperationType(OperationType.CREATE.toString());
event.setAuthDetails(new AuthDetailsRepresentation());
event.setRealmId(realm.toRepresentation().getId());
long currentTime = System.currentTimeMillis();
event.setTime(currentTime - 1000);
testingClient.testing("test").onAdminEvent(event, false);
event.setTime(currentTime);
testingClient.testing("test").onAdminEvent(event, false);
event.setTime(currentTime + 1000);
testingClient.testing("test").onAdminEvent(event, false);
List<AdminEventRepresentation> events = realm.getAdminEvents(null, null, null, null, null, null, null, currentTime, currentTime, null, null, null);
Assert.assertEquals(1, events.size());
events = realm.getAdminEvents(null, null, null, null, null, null, null, currentTime - 1000, currentTime + 1000, null, null, null);
Assert.assertEquals(3, events.size());
}
private void checkUpdateRealmEventsConfigEvent(int size) {
List<AdminEventRepresentation> events = events();

View File

@@ -17,11 +17,14 @@
package org.keycloak.testsuite.admin.event;
import org.hamcrest.MatcherAssert;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.events.EventType;
import org.keycloak.representations.idm.EventRepresentation;
import org.keycloak.testsuite.AssertEvents;
import java.util.Arrays;
import java.util.List;
@@ -71,6 +74,7 @@ public class LoginEventsTest extends AbstractEventTest {
List<EventRepresentation> events = events();
assertEquals(1, events.size());
EventRepresentation event = events.get(0);
MatcherAssert.assertThat(event.getId(), AssertEvents.isUUID());
assertTrue(event.getTime() > 0);
assertNotNull(event.getIpAddress());
assertEquals("LOGIN_ERROR", event.getType());
@@ -155,15 +159,41 @@ public class LoginEventsTest extends AbstractEventTest {
secondEvent.setType(EventType.LOGOUT.toString());
secondEvent.setTime(System.currentTimeMillis());
testingClient.testing("test").onEvent(firstEvent);
testingClient.testing("test").onEvent(secondEvent);
List<EventRepresentation> events = realm.getEvents(null, null, null, null, null, null, null, null);
List<EventRepresentation> events = realm.getEvents(null, null, null, null, null, null, null, null, "desc");
assertEquals(2, events.size());
assertEquals(EventType.LOGOUT.toString(), events.get(0).getType());
assertEquals(EventType.LOGIN.toString(), events.get(1).getType());
events = realm.getEvents(null, null, null, null, null, null, null, null, "asc");
assertEquals(2, events.size());
assertEquals(EventType.LOGOUT.toString(), events.get(1).getType());
assertEquals(EventType.LOGIN.toString(), events.get(0).getType());
}
@Test
public void filterByEpochTimeStamp() {
RealmResource realm = adminClient.realms().realm("test");
EventRepresentation event = new EventRepresentation();
event.setType(EventType.LOGIN.toString());
event.setRealmId(realm.toRepresentation().getId());
long currentTime = System.currentTimeMillis();
event.setTime(currentTime - 1000);
testingClient.testing("test").onEvent(event);
event.setTime(currentTime);
testingClient.testing("test").onEvent(event);
event.setTime(currentTime + 1000);
testingClient.testing("test").onEvent(event);
List<EventRepresentation> events = realm.getEvents(null, null, null, currentTime, currentTime, null, null, null, null);
Assert.assertEquals(1, events.size());
events = realm.getEvents(null, null, null, currentTime - 1000, currentTime + 1000, null, null, null, null);
Assert.assertEquals(3, events.size());
}
@Test