KEYCLOAK-8080 Audit the realm event configuration change

This commit is contained in:
rmartinc 2018-08-18 15:45:03 +02:00 committed by Hynek Mlnařík
parent dc8268b30c
commit 1b88eaf817
4 changed files with 148 additions and 24 deletions

View file

@ -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) {

View file

@ -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();
} }
/** /**

View file

@ -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());
}
} }

View file

@ -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