mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-25 16:42:34 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
10
docs/documentation/release_notes/topics/26_2_0.adoc
Normal file
10
docs/documentation/release_notes/topics/26_2_0.adoc
Normal 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.
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user