Issue #8749: Add an option to control the order of the event query and admin event query
This commit is contained in:
parent
1d2d3e5ca5
commit
7e5b45f999
13 changed files with 486 additions and 127 deletions
|
@ -50,6 +50,7 @@ public class JpaAdminEventQuery implements AdminEventQuery {
|
|||
private final ArrayList<Predicate> predicates;
|
||||
private Integer firstResult;
|
||||
private Integer maxResults;
|
||||
private boolean orderByDescTime = true;
|
||||
|
||||
public JpaAdminEventQuery(EntityManager em) {
|
||||
this.em = em;
|
||||
|
@ -143,13 +144,29 @@ public class JpaAdminEventQuery implements AdminEventQuery {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery orderByDescTime() {
|
||||
orderByDescTime = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery orderByAscTime() {
|
||||
orderByDescTime = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<AdminEvent> getResultStream() {
|
||||
if (!predicates.isEmpty()) {
|
||||
cq.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
|
||||
}
|
||||
|
||||
if (orderByDescTime) {
|
||||
cq.orderBy(cb.desc(root.get("time")));
|
||||
} else {
|
||||
cq.orderBy(cb.asc(root.get("time")));
|
||||
}
|
||||
|
||||
TypedQuery<AdminEventEntity> query = em.createQuery(cq);
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ public class JpaEventQuery implements EventQuery {
|
|||
private final ArrayList<Predicate> predicates;
|
||||
private Integer firstResult;
|
||||
private Integer maxResults;
|
||||
private boolean orderByDescTime = true;
|
||||
|
||||
public JpaEventQuery(EntityManager em) {
|
||||
this.em = em;
|
||||
|
@ -116,13 +117,29 @@ public class JpaEventQuery implements EventQuery {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery orderByDescTime() {
|
||||
orderByDescTime = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery orderByAscTime() {
|
||||
orderByDescTime = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Event> getResultStream() {
|
||||
if (!predicates.isEmpty()) {
|
||||
cq.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
|
||||
}
|
||||
|
||||
if(orderByDescTime) {
|
||||
cq.orderBy(cb.desc(root.get("time")));
|
||||
} else {
|
||||
cq.orderBy(cb.asc(root.get("time")));
|
||||
}
|
||||
|
||||
TypedQuery<EventEntity> query = em.createQuery(cq);
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.GE;
|
|||
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.IN;
|
||||
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LE;
|
||||
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LIKE;
|
||||
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
|
||||
import static org.keycloak.models.map.storage.QueryParameters.Order.DESCENDING;
|
||||
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
|
||||
|
||||
|
@ -42,6 +43,7 @@ public class MapAdminEventQuery implements AdminEventQuery {
|
|||
|
||||
private Integer firstResult;
|
||||
private Integer maxResults;
|
||||
private QueryParameters.Order order = DESCENDING;
|
||||
private DefaultModelCriteria<AdminEvent> mcb = criteria();
|
||||
private final Function<QueryParameters<AdminEvent>, Stream<AdminEvent>> resultProducer;
|
||||
|
||||
|
@ -121,12 +123,24 @@ public class MapAdminEventQuery implements AdminEventQuery {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery orderByDescTime() {
|
||||
order = DESCENDING;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AdminEventQuery orderByAscTime() {
|
||||
order = ASCENDING;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<AdminEvent> getResultStream() {
|
||||
return resultProducer.apply(QueryParameters.withCriteria(mcb)
|
||||
.offset(firstResult)
|
||||
.limit(maxResults)
|
||||
.orderBy(SearchableFields.TIMESTAMP, DESCENDING)
|
||||
.orderBy(SearchableFields.TIMESTAMP, order)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.EQ;
|
|||
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.GE;
|
||||
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.IN;
|
||||
import static org.keycloak.models.map.storage.ModelCriteriaBuilder.Operator.LE;
|
||||
import static org.keycloak.models.map.storage.QueryParameters.Order.ASCENDING;
|
||||
import static org.keycloak.models.map.storage.QueryParameters.Order.DESCENDING;
|
||||
import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.criteria;
|
||||
|
||||
|
@ -40,6 +41,7 @@ public class MapAuthEventQuery implements EventQuery {
|
|||
|
||||
private Integer firstResult;
|
||||
private Integer maxResults;
|
||||
private QueryParameters.Order order = DESCENDING;
|
||||
private DefaultModelCriteria<Event> mcb = criteria();
|
||||
private final Function<QueryParameters<Event>, Stream<Event>> resultProducer;
|
||||
|
||||
|
@ -101,11 +103,23 @@ public class MapAuthEventQuery implements EventQuery {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery orderByDescTime() {
|
||||
order = DESCENDING;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EventQuery orderByAscTime() {
|
||||
order = ASCENDING;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<Event> getResultStream() {
|
||||
return resultProducer.apply(QueryParameters.withCriteria(mcb)
|
||||
.offset(firstResult)
|
||||
.limit(maxResults)
|
||||
.orderBy(SearchableFields.TIMESTAMP, DESCENDING));
|
||||
.orderBy(SearchableFields.TIMESTAMP, order));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,20 @@ public interface EventQuery {
|
|||
*/
|
||||
EventQuery maxResults(int max);
|
||||
|
||||
/**
|
||||
* Order the result by descending time
|
||||
*
|
||||
* @return <code>this</code> for method chaining
|
||||
*/
|
||||
EventQuery orderByDescTime();
|
||||
|
||||
/**
|
||||
* Order the result by ascending time
|
||||
*
|
||||
* @return <code>this</code> for method chaining
|
||||
*/
|
||||
EventQuery orderByAscTime();
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getResultStream() getResultStream} instead.
|
||||
*/
|
||||
|
|
|
@ -127,6 +127,20 @@ public interface AdminEventQuery {
|
|||
*/
|
||||
AdminEventQuery maxResults(int max);
|
||||
|
||||
/**
|
||||
* Order the result by descending time
|
||||
*
|
||||
* @return <code>this</code> for method chaining
|
||||
*/
|
||||
AdminEventQuery orderByDescTime();
|
||||
|
||||
/**
|
||||
* Order the result by ascending time
|
||||
*
|
||||
* @return <code>this</code> for method chaining
|
||||
*/
|
||||
AdminEventQuery orderByAscTime();
|
||||
|
||||
/**
|
||||
* Executes the query and returns the results
|
||||
* @deprecated Use {@link #getResultStream() getResultStream} instead.
|
||||
|
|
|
@ -77,7 +77,7 @@ public class ProtectionService {
|
|||
KeycloakSession keycloakSession = authorization.getKeycloakSession();
|
||||
UserModel serviceAccount = keycloakSession.users().getServiceAccount(client);
|
||||
AdminEventBuilder adminEvent = new AdminEventBuilder(realm, new AdminAuth(realm, identity.getAccessToken(), serviceAccount, client), keycloakSession, clientConnection);
|
||||
return adminEvent.realm(realm).authClient(client).authUser(serviceAccount);
|
||||
return adminEvent;
|
||||
}
|
||||
|
||||
@Path("/permission")
|
||||
|
|
|
@ -55,7 +55,7 @@ public class AdminEventBuilder {
|
|||
this.listeners = new HashMap<>();
|
||||
updateStore(session);
|
||||
addListeners(session);
|
||||
|
||||
realm(realm);
|
||||
authRealm(auth.getRealm());
|
||||
authClient(auth.getClient());
|
||||
authUser(auth.getUser());
|
||||
|
|
|
@ -143,7 +143,7 @@ public class RealmAdminResource {
|
|||
this.auth = auth;
|
||||
this.realm = realm;
|
||||
this.tokenManager = tokenManager;
|
||||
this.adminEvent = adminEvent.realm(realm).resource(ResourceType.REALM);
|
||||
this.adminEvent = adminEvent.resource(ResourceType.REALM);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -701,7 +701,7 @@ public class RealmAdminResource {
|
|||
|
||||
logger.debug("updating realm events config: " + realm.getName());
|
||||
new RealmManager(session).updateRealmEventsConfig(rep, realm);
|
||||
adminEvent.operation(OperationType.UPDATE).resource(ResourceType.REALM).realm(realm)
|
||||
adminEvent.operation(OperationType.UPDATE).resource(ResourceType.REALM)
|
||||
.resourcePath(session.getContext().getUri()).representation(rep)
|
||||
// refresh the builder to consider old and new config
|
||||
.refreshRealmEventsConfig(session)
|
||||
|
|
|
@ -160,6 +160,31 @@ public class AdminEventTest extends AbstractEventTest {
|
|||
assertThat(realm.getAdminEvents(null, null, null, null, null, null, null, null, 0, 1000).size(), is(greaterThanOrEqualTo(110)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orderResultsTest() {
|
||||
RealmResource realm = adminClient.realms().realm("test");
|
||||
AdminEventRepresentation firstEvent = new AdminEventRepresentation();
|
||||
firstEvent.setOperationType(OperationType.CREATE.toString());
|
||||
firstEvent.setAuthDetails(new AuthDetailsRepresentation());
|
||||
firstEvent.setRealmId(realm.toRepresentation().getId());
|
||||
firstEvent.setTime(System.currentTimeMillis() - 1000);
|
||||
|
||||
AdminEventRepresentation secondEvent = new AdminEventRepresentation();
|
||||
secondEvent.setOperationType(OperationType.DELETE.toString());
|
||||
secondEvent.setAuthDetails(new AuthDetailsRepresentation());
|
||||
secondEvent.setRealmId(realm.toRepresentation().getId());
|
||||
secondEvent.setTime(System.currentTimeMillis());
|
||||
|
||||
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);
|
||||
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())));
|
||||
}
|
||||
|
||||
|
||||
private void checkUpdateRealmEventsConfigEvent(int size) {
|
||||
List<AdminEventRepresentation> events = events();
|
||||
assertThat(events.size(), is(equalTo(size)));
|
||||
|
|
|
@ -152,6 +152,30 @@ public class LoginEventsTest extends AbstractEventTest {
|
|||
assertTrue(realm.getEvents(null, null, null, null, null, null, 0, 1000).size() >= 110);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void orderResultsTest() {
|
||||
RealmResource realm = adminClient.realms().realm("test");
|
||||
EventRepresentation firstEvent = new EventRepresentation();
|
||||
firstEvent.setRealmId(realm.toRepresentation().getId());
|
||||
firstEvent.setType(EventType.LOGIN.toString());
|
||||
firstEvent.setTime(System.currentTimeMillis() - 1000);
|
||||
|
||||
EventRepresentation secondEvent = new EventRepresentation();
|
||||
secondEvent.setRealmId(realm.toRepresentation().getId());
|
||||
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);
|
||||
assertEquals(2, events.size());
|
||||
assertEquals(EventType.LOGOUT.toString(), events.get(0).getType());
|
||||
assertEquals(EventType.LOGIN.toString(), events.get(1).getType());
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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
|
||||
|
|
|
@ -16,32 +16,30 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.model.events;
|
||||
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventStoreProvider;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.testsuite.model.KeycloakModelTest;
|
||||
import org.keycloak.testsuite.model.RequireProvider;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.Test;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.events.EventStoreProvider;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.delegate.ClientModelLazyDelegate;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
import org.keycloak.services.resources.admin.AdminAuth;
|
||||
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
||||
import org.keycloak.testsuite.model.KeycloakModelTest;
|
||||
import org.keycloak.testsuite.model.RequireProvider;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RequireProvider(EventStoreProvider.class)
|
||||
public class AdminEventQueryTest extends KeycloakModelTest {
|
||||
|
||||
|
@ -56,128 +54,71 @@ public class AdminEventQueryTest extends KeycloakModelTest {
|
|||
|
||||
@Override
|
||||
public void cleanEnvironment(KeycloakSession s) {
|
||||
EventStoreProvider eventStore = s.getProvider(EventStoreProvider.class);
|
||||
eventStore.clearAdmin(s.realms().getRealm(realmId));
|
||||
s.realms().removeRealm(realmId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() {
|
||||
inRolledBackTransaction(null, (session, t) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
eventStore.clear();
|
||||
});
|
||||
}
|
||||
|
||||
private Event createAuthEventForUser(RealmModel realm, String user) {
|
||||
return new EventBuilder(realm, null, DummyClientConnection.DUMMY_CONNECTION)
|
||||
.event(EventType.LOGIN)
|
||||
.user(user)
|
||||
.getEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuery() {
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
eventStore.onEvent(createAuthEventForUser(realm,"u1"));
|
||||
eventStore.onEvent(createAuthEventForUser(realm,"u2"));
|
||||
eventStore.onEvent(createAuthEventForUser(realm,"u3"));
|
||||
eventStore.onEvent(createAuthEventForUser(realm,"u4"));
|
||||
|
||||
return realm.getId();
|
||||
eventStore.onEvent(createClientEvent(realm, OperationType.CREATE), false);
|
||||
eventStore.onEvent(createClientEvent(realm, OperationType.UPDATE), false);
|
||||
eventStore.onEvent(createClientEvent(realm, OperationType.DELETE), false);
|
||||
eventStore.onEvent(createClientEvent(realm, OperationType.CREATE), false);
|
||||
return null;
|
||||
});
|
||||
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
assertThat(eventStore.createQuery()
|
||||
assertThat(eventStore.createAdminQuery()
|
||||
.firstResult(2)
|
||||
.getResultStream()
|
||||
.collect(Collectors.counting()),
|
||||
is(2L)
|
||||
);
|
||||
|
||||
is(2L));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequireProvider(value = EventStoreProvider.class, only = "map")
|
||||
public void testEventExpiration() {
|
||||
public void testQueryOrder() {
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
AdminEvent firstEvent = createClientEvent(realm, OperationType.CREATE);
|
||||
firstEvent.setTime(1L);
|
||||
AdminEvent secondEvent = createClientEvent(realm, OperationType.DELETE);
|
||||
secondEvent.setTime(2L);
|
||||
eventStore.onEvent(firstEvent, false);
|
||||
eventStore.onEvent(secondEvent, false);
|
||||
List<AdminEvent> adminEventsAsc = eventStore.createAdminQuery()
|
||||
.orderByAscTime()
|
||||
.getResultStream()
|
||||
.collect(Collectors.toList());
|
||||
assertThat(adminEventsAsc.size(), is(2));
|
||||
assertThat(adminEventsAsc.get(0).getOperationType(), is(OperationType.CREATE));
|
||||
assertThat(adminEventsAsc.get(1).getOperationType(), is(OperationType.DELETE));
|
||||
|
||||
// Set expiration so no event is valid
|
||||
realm.setEventsExpiration(5);
|
||||
Event e = createAuthEventForUser(realm, "u1");
|
||||
eventStore.onEvent(e);
|
||||
|
||||
// Set expiration to 1000 seconds
|
||||
realm.setEventsExpiration(1000);
|
||||
e = createAuthEventForUser(realm, "u2");
|
||||
eventStore.onEvent(e);
|
||||
|
||||
List<AdminEvent> adminEventsDesc = eventStore.createAdminQuery()
|
||||
.orderByDescTime()
|
||||
.getResultStream()
|
||||
.collect(Collectors.toList());
|
||||
assertThat(adminEventsDesc.size(), is(2));
|
||||
assertThat(adminEventsDesc.get(0).getOperationType(), is(OperationType.DELETE));
|
||||
assertThat(adminEventsDesc.get(1).getOperationType(), is(OperationType.CREATE));
|
||||
return null;
|
||||
});
|
||||
|
||||
Time.setOffset(10);
|
||||
|
||||
try {
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
|
||||
Set<Event> events = eventStore.createQuery()
|
||||
.getResultStream().collect(Collectors.toSet());
|
||||
|
||||
assertThat(events, hasSize(1));
|
||||
assertThat(events.iterator().next().getUserId(), equalTo("u2"));
|
||||
return null;
|
||||
});
|
||||
} finally {
|
||||
Time.setOffset(0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequireProvider(value = EventStoreProvider.class, only = "map")
|
||||
public void testEventsClearedOnRealmRemoval() {
|
||||
// Create another realm
|
||||
String newRealmId = inComittedTransaction(null, (session, t) -> {
|
||||
RealmModel realm = session.realms().createRealm("events-realm");
|
||||
realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
Event e = createAuthEventForUser(realm, "u1");
|
||||
eventStore.onEvent(e);
|
||||
|
||||
AdminEvent ae = new AdminEvent();
|
||||
ae.setRealmId(realm.getId());
|
||||
eventStore.onEvent(ae, false);
|
||||
|
||||
return realm.getId();
|
||||
});
|
||||
|
||||
// Check if events were created
|
||||
inComittedTransaction(session -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
assertThat(eventStore.createQuery().getResultStream().count(), is(1L));
|
||||
assertThat(eventStore.createAdminQuery().getResultStream().count(), is(1L));
|
||||
});
|
||||
|
||||
// Remove realm
|
||||
inComittedTransaction((Consumer<KeycloakSession>) session -> session.realms().removeRealm(newRealmId));
|
||||
|
||||
// Check events were removed
|
||||
inComittedTransaction(session -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
assertThat(eventStore.createQuery().getResultStream().count(), is(0L));
|
||||
assertThat(eventStore.createAdminQuery().getResultStream().count(), is(0L));
|
||||
});
|
||||
private AdminEvent createClientEvent(RealmModel realm, OperationType operation) {
|
||||
return new AdminEventBuilder(realm, new DummyAuth(realm), null, DummyClientConnection.DUMMY_CONNECTION)
|
||||
.resource(ResourceType.CLIENT).operation(operation).getEvent();
|
||||
}
|
||||
|
||||
private static class DummyClientConnection implements ClientConnection {
|
||||
|
||||
private static DummyClientConnection DUMMY_CONNECTION = new DummyClientConnection();
|
||||
private static final AdminEventQueryTest.DummyClientConnection DUMMY_CONNECTION =
|
||||
new AdminEventQueryTest.DummyClientConnection();
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
|
@ -205,4 +146,34 @@ public class AdminEventQueryTest extends KeycloakModelTest {
|
|||
}
|
||||
}
|
||||
|
||||
private static class DummyAuth extends AdminAuth {
|
||||
|
||||
private final RealmModel realm;
|
||||
|
||||
public DummyAuth(RealmModel realm) {
|
||||
super(realm, null, null, null);
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RealmModel getRealm() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientModel getClient() {
|
||||
return new ClientModelLazyDelegate.WithId("dummy-client", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUser() {
|
||||
return new UserModelDelegate(null) {
|
||||
@Override
|
||||
public String getId() {
|
||||
return "dummy-user";
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.testsuite.model.events;
|
||||
|
||||
import org.keycloak.common.ClientConnection;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventStoreProvider;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.testsuite.model.KeycloakModelTest;
|
||||
import org.keycloak.testsuite.model.RequireProvider;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.Test;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
@RequireProvider(EventStoreProvider.class)
|
||||
public class EventQueryTest extends KeycloakModelTest {
|
||||
|
||||
private String realmId;
|
||||
|
||||
@Override
|
||||
public void createEnvironment(KeycloakSession s) {
|
||||
RealmModel realm = s.realms().createRealm("realm");
|
||||
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||
this.realmId = realm.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanEnvironment(KeycloakSession s) {
|
||||
s.realms().removeRealm(realmId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClear() {
|
||||
inRolledBackTransaction(null, (session, t) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
eventStore.clear();
|
||||
});
|
||||
}
|
||||
|
||||
private Event createAuthEventForUser(RealmModel realm, String user) {
|
||||
return new EventBuilder(realm, null, DummyClientConnection.DUMMY_CONNECTION)
|
||||
.event(EventType.LOGIN)
|
||||
.user(user)
|
||||
.getEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQuery() {
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
|
||||
eventStore.onEvent(createAuthEventForUser(realm,"u1"));
|
||||
eventStore.onEvent(createAuthEventForUser(realm,"u2"));
|
||||
eventStore.onEvent(createAuthEventForUser(realm,"u3"));
|
||||
eventStore.onEvent(createAuthEventForUser(realm,"u4"));
|
||||
|
||||
return realm.getId();
|
||||
});
|
||||
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
assertThat(eventStore.createQuery()
|
||||
.firstResult(2)
|
||||
.getResultStream()
|
||||
.collect(Collectors.counting()),
|
||||
is(2L)
|
||||
);
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryOrder() {
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
|
||||
Event firstEvent = createAuthEventForUser(realm, "u1");
|
||||
firstEvent.setTime(1L);
|
||||
Event secondEvent = createAuthEventForUser(realm, "u2");
|
||||
secondEvent.setTime(2L);
|
||||
eventStore.onEvent(firstEvent);
|
||||
eventStore.onEvent(secondEvent);
|
||||
|
||||
return realm.getId();
|
||||
});
|
||||
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
List<Event> eventsAsc = eventStore.createQuery()
|
||||
.realm(realmId)
|
||||
.orderByAscTime()
|
||||
.getResultStream()
|
||||
.collect(Collectors.toList());
|
||||
assertThat(eventsAsc.size(), is(2));
|
||||
assertThat(eventsAsc.get(0).getUserId(), is("u1"));
|
||||
assertThat(eventsAsc.get(1).getUserId(), is("u2"));
|
||||
|
||||
List<Event> eventsDesc = eventStore.createQuery()
|
||||
.realm(realmId)
|
||||
.orderByDescTime()
|
||||
.getResultStream()
|
||||
.collect(Collectors.toList());
|
||||
assertThat(eventsDesc.size(), is(2));
|
||||
assertThat(eventsDesc.get(0).getUserId(), is("u2"));
|
||||
assertThat(eventsDesc.get(1).getUserId(), is("u1"));
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@RequireProvider(value = EventStoreProvider.class, only = "map")
|
||||
public void testEventExpiration() {
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
|
||||
// Set expiration so no event is valid
|
||||
realm.setEventsExpiration(5);
|
||||
Event e = createAuthEventForUser(realm, "u1");
|
||||
eventStore.onEvent(e);
|
||||
|
||||
// Set expiration to 1000 seconds
|
||||
realm.setEventsExpiration(1000);
|
||||
e = createAuthEventForUser(realm, "u2");
|
||||
eventStore.onEvent(e);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
Time.setOffset(10);
|
||||
|
||||
try {
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
|
||||
Set<Event> events = eventStore.createQuery()
|
||||
.getResultStream().collect(Collectors.toSet());
|
||||
|
||||
assertThat(events, hasSize(1));
|
||||
assertThat(events.iterator().next().getUserId(), equalTo("u2"));
|
||||
return null;
|
||||
});
|
||||
} finally {
|
||||
Time.setOffset(0);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequireProvider(value = EventStoreProvider.class, only = "map")
|
||||
public void testEventsClearedOnRealmRemoval() {
|
||||
// Create another realm
|
||||
String newRealmId = inComittedTransaction(null, (session, t) -> {
|
||||
RealmModel realm = session.realms().createRealm("events-realm");
|
||||
realm.setDefaultRole(session.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
Event e = createAuthEventForUser(realm, "u1");
|
||||
eventStore.onEvent(e);
|
||||
|
||||
AdminEvent ae = new AdminEvent();
|
||||
ae.setRealmId(realm.getId());
|
||||
eventStore.onEvent(ae, false);
|
||||
|
||||
return realm.getId();
|
||||
});
|
||||
|
||||
// Check if events were created
|
||||
inComittedTransaction(session -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
assertThat(eventStore.createQuery().getResultStream().count(), is(1L));
|
||||
assertThat(eventStore.createAdminQuery().getResultStream().count(), is(1L));
|
||||
});
|
||||
|
||||
// Remove realm
|
||||
inComittedTransaction((Consumer<KeycloakSession>) session -> session.realms().removeRealm(newRealmId));
|
||||
|
||||
// Check events were removed
|
||||
inComittedTransaction(session -> {
|
||||
EventStoreProvider eventStore = session.getProvider(EventStoreProvider.class);
|
||||
assertThat(eventStore.createQuery().getResultStream().count(), is(0L));
|
||||
assertThat(eventStore.createAdminQuery().getResultStream().count(), is(0L));
|
||||
});
|
||||
}
|
||||
|
||||
private static class DummyClientConnection implements ClientConnection {
|
||||
|
||||
private static DummyClientConnection DUMMY_CONNECTION = new DummyClientConnection();
|
||||
|
||||
@Override
|
||||
public String getRemoteAddr() {
|
||||
return "remoteAddr";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteHost() {
|
||||
return "remoteHost";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemotePort() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalAddr() {
|
||||
return "localAddr";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLocalPort() {
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue