KEYCLOAK-8080 Audit the realm event configuration change
This commit is contained in:
parent
dc8268b30c
commit
1b88eaf817
4 changed files with 148 additions and 24 deletions
|
@ -34,15 +34,16 @@ import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.LinkedList;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class AdminEventBuilder {
|
public class AdminEventBuilder {
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(AdminEventBuilder.class);
|
protected static final Logger logger = Logger.getLogger(AdminEventBuilder.class);
|
||||||
|
|
||||||
private EventStoreProvider store;
|
private EventStoreProvider store;
|
||||||
private List<EventListenerProvider> listeners;
|
private Map<String, EventListenerProvider> listeners;
|
||||||
private RealmModel realm;
|
private RealmModel realm;
|
||||||
private AdminEvent adminEvent;
|
private AdminEvent adminEvent;
|
||||||
|
|
||||||
|
@ -50,26 +51,9 @@ public class AdminEventBuilder {
|
||||||
this.realm = realm;
|
this.realm = realm;
|
||||||
adminEvent = new AdminEvent();
|
adminEvent = new AdminEvent();
|
||||||
|
|
||||||
if (realm.isAdminEventsEnabled()) {
|
this.listeners = new HashMap<>();
|
||||||
EventStoreProvider store = session.getProvider(EventStoreProvider.class);
|
updateStore(session);
|
||||||
if (store != null) {
|
addListeners(session);
|
||||||
this.store = store;
|
|
||||||
} else {
|
|
||||||
ServicesLogger.LOGGER.noEventStoreProvider();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (realm.getEventsListeners() != null && !realm.getEventsListeners().isEmpty()) {
|
|
||||||
this.listeners = new LinkedList<>();
|
|
||||||
for (String id : realm.getEventsListeners()) {
|
|
||||||
EventListenerProvider listener = session.getProvider(EventListenerProvider.class, id);
|
|
||||||
if (listener != null) {
|
|
||||||
listeners.add(listener);
|
|
||||||
} else {
|
|
||||||
ServicesLogger.LOGGER.providerNotFound(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
authRealm(auth.getRealm());
|
authRealm(auth.getRealm());
|
||||||
authClient(auth.getClient());
|
authClient(auth.getClient());
|
||||||
|
@ -87,6 +71,45 @@ public class AdminEventBuilder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes the builder assuming that the realm event information has
|
||||||
|
* changed. Thought to be used when the updateRealmEventsConfig has
|
||||||
|
* modified the events configuration. Now the store and the listeners are
|
||||||
|
* updated to have previous and new setup.
|
||||||
|
* @param session The session
|
||||||
|
* @return The same builder
|
||||||
|
*/
|
||||||
|
public AdminEventBuilder refreshRealmEventsConfig(KeycloakSession session) {
|
||||||
|
return this.updateStore(session).addListeners(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminEventBuilder updateStore(KeycloakSession session) {
|
||||||
|
if (realm.isAdminEventsEnabled() && store == null) {
|
||||||
|
this.store = session.getProvider(EventStoreProvider.class);
|
||||||
|
if (store == null) {
|
||||||
|
ServicesLogger.LOGGER.noEventStoreProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AdminEventBuilder addListeners(KeycloakSession session) {
|
||||||
|
Set<String> extraListeners = realm.getEventsListeners();
|
||||||
|
if (extraListeners != null && !extraListeners.isEmpty()) {
|
||||||
|
for (String id : extraListeners) {
|
||||||
|
if (!listeners.containsKey(id)) {
|
||||||
|
EventListenerProvider listener = session.getProvider(EventListenerProvider.class, id);
|
||||||
|
if (listener != null) {
|
||||||
|
listeners.put(id, listener);
|
||||||
|
} else {
|
||||||
|
ServicesLogger.LOGGER.providerNotFound(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public AdminEventBuilder operation(OperationType operationType) {
|
public AdminEventBuilder operation(OperationType operationType) {
|
||||||
adminEvent.setOperationType(operationType);
|
adminEvent.setOperationType(operationType);
|
||||||
return this;
|
return this;
|
||||||
|
@ -220,7 +243,7 @@ public class AdminEventBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listeners != null) {
|
if (listeners != null) {
|
||||||
for (EventListenerProvider l : listeners) {
|
for (EventListenerProvider l : listeners.values()) {
|
||||||
try {
|
try {
|
||||||
l.onEvent(adminEvent, includeRepresentation);
|
l.onEvent(adminEvent, includeRepresentation);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
|
|
|
@ -680,6 +680,11 @@ public class RealmAdminResource {
|
||||||
|
|
||||||
logger.debug("updating realm events config: " + realm.getName());
|
logger.debug("updating realm events config: " + realm.getName());
|
||||||
new RealmManager(session).updateRealmEventsConfig(rep, realm);
|
new RealmManager(session).updateRealmEventsConfig(rep, realm);
|
||||||
|
adminEvent.operation(OperationType.UPDATE).resource(ResourceType.REALM).realm(realm)
|
||||||
|
.resourcePath(session.getContext().getUri()).representation(rep)
|
||||||
|
// refresh the builder to consider old and new config
|
||||||
|
.refreshRealmEventsConfig(session)
|
||||||
|
.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -39,6 +39,7 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test getting and filtering admin events.
|
* Test getting and filtering admin events.
|
||||||
|
@ -153,4 +154,37 @@ public class AdminEventTest extends AbstractEventTest {
|
||||||
assertTrue(realm.getAdminEvents(null, null, null, null, null, null, null, null, 0, 1000).size() >= 110);
|
assertTrue(realm.getAdminEvents(null, null, null, null, null, null, null, null, 0, 1000).size() >= 110);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkupdateRealmEventsConfigEvent(int size) {
|
||||||
|
List<AdminEventRepresentation> events = events();
|
||||||
|
assertEquals(size, events.size());
|
||||||
|
AdminEventRepresentation event = events().get(0);assertEquals("UPDATE", event.getOperationType());
|
||||||
|
assertEquals(realmName(), event.getRealmId());
|
||||||
|
assertEquals("events/config", event.getResourcePath());
|
||||||
|
assertEquals("master", event.getAuthDetails().getRealmId());
|
||||||
|
assertNotNull(event.getRepresentation());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateRealmEventsConfig() {
|
||||||
|
// change from OFF to ON should be stored
|
||||||
|
configRep.setAdminEventsDetailsEnabled(Boolean.TRUE);
|
||||||
|
configRep.setAdminEventsEnabled(Boolean.TRUE);
|
||||||
|
saveConfig();
|
||||||
|
checkupdateRealmEventsConfigEvent(1);
|
||||||
|
|
||||||
|
// any other change should be store too
|
||||||
|
configRep.setEventsEnabled(Boolean.TRUE);
|
||||||
|
saveConfig();
|
||||||
|
checkupdateRealmEventsConfigEvent(2);
|
||||||
|
|
||||||
|
// change from ON to OFF should be stored too
|
||||||
|
configRep.setAdminEventsEnabled(Boolean.FALSE);
|
||||||
|
saveConfig();
|
||||||
|
checkupdateRealmEventsConfigEvent(3);
|
||||||
|
|
||||||
|
// another change should not be stored cos it was OFF already
|
||||||
|
configRep.setAdminEventsDetailsEnabled(Boolean.FALSE);
|
||||||
|
saveConfig();
|
||||||
|
assertEquals(3, events().size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,10 @@ import java.util.Map;
|
||||||
import javax.ws.rs.BadRequestException;
|
import javax.ws.rs.BadRequestException;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.events.log.JBossLoggingEventListenerProviderFactory;
|
||||||
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
|
import org.keycloak.testsuite.events.EventsListenerProviderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -282,6 +286,64 @@ public class RealmTest extends AbstractAdminTest {
|
||||||
adminClient.realms().realm("test-immutable").remove();
|
adminClient.realms().realm("test-immutable").remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RealmEventsConfigRepresentation copyRealmEventsConfigRepresentation(RealmEventsConfigRepresentation rep) {
|
||||||
|
RealmEventsConfigRepresentation recr = new RealmEventsConfigRepresentation();
|
||||||
|
recr.setEnabledEventTypes(rep.getEnabledEventTypes());
|
||||||
|
recr.setEventsListeners(rep.getEventsListeners());
|
||||||
|
recr.setEventsExpiration(rep.getEventsExpiration());
|
||||||
|
recr.setEventsEnabled(rep.isEventsEnabled());
|
||||||
|
recr.setAdminEventsEnabled(rep.isAdminEventsEnabled());
|
||||||
|
recr.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
|
||||||
|
return recr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkRealmEventsConfigRepresentation(RealmEventsConfigRepresentation expected,
|
||||||
|
RealmEventsConfigRepresentation actual) {
|
||||||
|
assertEquals(expected.getEnabledEventTypes().size(), actual.getEnabledEventTypes().size());
|
||||||
|
assertTrue(actual.getEnabledEventTypes().containsAll(expected.getEnabledEventTypes()));
|
||||||
|
assertEquals(expected.getEventsListeners().size(), actual.getEventsListeners().size());
|
||||||
|
assertTrue(actual.getEventsListeners().containsAll(expected.getEventsListeners()));
|
||||||
|
assertEquals(expected.getEventsExpiration(), actual.getEventsExpiration());
|
||||||
|
assertEquals(expected.isEventsEnabled(), actual.isEventsEnabled());
|
||||||
|
assertEquals(expected.isAdminEventsEnabled(), actual.isAdminEventsEnabled());
|
||||||
|
assertEquals(expected.isAdminEventsDetailsEnabled(), actual.isAdminEventsDetailsEnabled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void updateRealmEventsConfig() {
|
||||||
|
RealmEventsConfigRepresentation rep = realm.getRealmEventsConfig();
|
||||||
|
RealmEventsConfigRepresentation repOrig = copyRealmEventsConfigRepresentation(rep);
|
||||||
|
|
||||||
|
// the "event-queue" listener should be enabled by default
|
||||||
|
assertTrue("event-queue should be enabled initially", rep.getEventsListeners().contains(EventsListenerProviderFactory.PROVIDER_ID));
|
||||||
|
|
||||||
|
// first modification => remove "event-queue", should be sent to the queue
|
||||||
|
rep.setEnabledEventTypes(Arrays.asList(EventType.LOGIN.name(), EventType.LOGIN_ERROR.name()));
|
||||||
|
rep.setEventsListeners(Arrays.asList(JBossLoggingEventListenerProviderFactory.ID));
|
||||||
|
rep.setEventsExpiration(36000L);
|
||||||
|
rep.setEventsEnabled(true);
|
||||||
|
rep.setAdminEventsEnabled(true);
|
||||||
|
rep.setAdminEventsDetailsEnabled(true);
|
||||||
|
adminClient.realms().realm(REALM_NAME).updateRealmEventsConfig(rep);
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, "events/config", rep, ResourceType.REALM);
|
||||||
|
RealmEventsConfigRepresentation actual = realm.getRealmEventsConfig();
|
||||||
|
checkRealmEventsConfigRepresentation(rep, actual);
|
||||||
|
|
||||||
|
// second modification => should not be sent cos event-queue was removed in the first mod
|
||||||
|
rep.setEnabledEventTypes(Arrays.asList(EventType.LOGIN.name(),
|
||||||
|
EventType.LOGIN_ERROR.name(), EventType.CLIENT_LOGIN.name()));
|
||||||
|
adminClient.realms().realm(REALM_NAME).updateRealmEventsConfig(rep);
|
||||||
|
assertAdminEvents.assertEmpty();
|
||||||
|
actual = realm.getRealmEventsConfig();
|
||||||
|
checkRealmEventsConfigRepresentation(rep, actual);
|
||||||
|
|
||||||
|
// third modification => restore queue => should be sent and recovered
|
||||||
|
adminClient.realms().realm(REALM_NAME).updateRealmEventsConfig(repOrig);
|
||||||
|
assertAdminEvents.assertEvent(realmId, OperationType.UPDATE, "events/config", repOrig, ResourceType.REALM);
|
||||||
|
actual = realm.getRealmEventsConfig();
|
||||||
|
checkRealmEventsConfigRepresentation(repOrig, actual);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void updateRealm() {
|
public void updateRealm() {
|
||||||
// first change
|
// first change
|
||||||
|
|
Loading…
Reference in a new issue