parent
62bdac4717
commit
db929056f3
8 changed files with 225 additions and 53 deletions
17
README.md
17
README.md
|
@ -43,6 +43,23 @@ keycloak_event_admin_total{error="",operation="CREATE",realm="1fdb3465-1675-49e8
|
||||||
keycloak_event_admin_total{error="",operation="CREATE",realm="9039a0b5-e8c9-437a-a02e-9d91b04548a4",resource="USER",} 1.0
|
keycloak_event_admin_total{error="",operation="CREATE",realm="9039a0b5-e8c9-437a-a02e-9d91b04548a4",resource="USER",} 1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### `KC_METRICS_EVENT_REPLACE_IDS`
|
||||||
|
|
||||||
|
If set to `true` than replace model ids with names:
|
||||||
|
|
||||||
|
* [RealmModel#getId()](https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/RealmModel.java#L82) with [RealmModel#getName()](https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/RealmModel.java#L84)
|
||||||
|
* [ClientModel#getId()](https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/ClientModel.java#L106) with [ClientModel#getClientId()](https://github.com/keycloak/keycloak/blob/main/server-spi/src/main/java/org/keycloak/models/ClientModel.java#L112)
|
||||||
|
|
||||||
|
Metrics:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
keycloak_event_user_total{client="test-client",error="",realm="test-realm",type="LOGIN",} 2.0
|
||||||
|
keycloak_event_user_total{client="other-client",error="",realm="other-realm",type="LOGIN",} 1.0
|
||||||
|
keycloak_event_user_total{client="other-client",error="invalid_user_credentials",realm="other-realm",type="LOGIN_ERROR",} 1.0
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Testcontainers
|
### Testcontainers
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package io.kokuwa.keycloak.metrics;
|
package io.kokuwa.keycloak.metrics;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.events.Event;
|
import org.keycloak.events.Event;
|
||||||
import org.keycloak.events.EventListenerProvider;
|
import org.keycloak.events.EventListenerProvider;
|
||||||
import org.keycloak.events.admin.AdminEvent;
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
|
||||||
|
@ -13,18 +17,23 @@ import io.micrometer.core.instrument.MeterRegistry;
|
||||||
*/
|
*/
|
||||||
public class MicrometerEventListener implements EventListenerProvider, AutoCloseable {
|
public class MicrometerEventListener implements EventListenerProvider, AutoCloseable {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(MicrometerEventListener.class);
|
||||||
private final MeterRegistry registry;
|
private final MeterRegistry registry;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final boolean replace;
|
||||||
|
|
||||||
public MicrometerEventListener(MeterRegistry registry) {
|
public MicrometerEventListener(MeterRegistry registry, KeycloakSession session, boolean replaceId) {
|
||||||
this.registry = registry;
|
this.registry = registry;
|
||||||
|
this.session = session;
|
||||||
|
this.replace = replaceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(Event event) {
|
public void onEvent(Event event) {
|
||||||
registry.counter("keycloak_event_user",
|
registry.counter("keycloak_event_user",
|
||||||
"realm", toBlank(event.getRealmId()),
|
"realm", toBlank(replace ? getRealmName(event.getRealmId()) : event.getRealmId()),
|
||||||
"type", toBlank(event.getType()),
|
"type", toBlank(event.getType()),
|
||||||
"client", toBlank(event.getClientId()),
|
"client", toBlank(replace ? getClientId(event.getRealmId(), event.getClientId()) : event.getClientId()),
|
||||||
"error", toBlank(event.getError()))
|
"error", toBlank(event.getError()))
|
||||||
.increment();
|
.increment();
|
||||||
}
|
}
|
||||||
|
@ -32,7 +41,7 @@ public class MicrometerEventListener implements EventListenerProvider, AutoClose
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
||||||
registry.counter("keycloak_event_admin",
|
registry.counter("keycloak_event_admin",
|
||||||
"realm", toBlank(event.getRealmId()),
|
"realm", toBlank(replace ? getRealmName(event.getRealmId()) : event.getRealmId()),
|
||||||
"resource", toBlank(event.getResourceType()),
|
"resource", toBlank(event.getResourceType()),
|
||||||
"operation", toBlank(event.getOperationType()),
|
"operation", toBlank(event.getOperationType()),
|
||||||
"error", toBlank(event.getError()))
|
"error", toBlank(event.getError()))
|
||||||
|
@ -45,4 +54,24 @@ public class MicrometerEventListener implements EventListenerProvider, AutoClose
|
||||||
private String toBlank(Object value) {
|
private String toBlank(Object value) {
|
||||||
return value == null ? "" : value.toString();
|
return value == null ? "" : value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getRealmName(String realmId) {
|
||||||
|
var model = session.realms().getRealm(realmId);
|
||||||
|
if (model == null) {
|
||||||
|
log.warnv("Failed to resolve realm with id", realmId);
|
||||||
|
return realmId;
|
||||||
|
}
|
||||||
|
return model.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClientId(String realmId, String clientId) {
|
||||||
|
var model = Optional.ofNullable(session.realms().getRealm(realmId))
|
||||||
|
.map(realm -> realm.getClientById(clientId))
|
||||||
|
.orElse(null);
|
||||||
|
if (model == null) {
|
||||||
|
log.warnv("Failed to resolve client with id {} in realm {}", clientId, realmId);
|
||||||
|
return clientId;
|
||||||
|
}
|
||||||
|
return model.getClientId();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.kokuwa.keycloak.metrics;
|
||||||
|
|
||||||
import javax.enterprise.inject.spi.CDI;
|
import javax.enterprise.inject.spi.CDI;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config.Scope;
|
import org.keycloak.Config.Scope;
|
||||||
import org.keycloak.events.EventListenerProvider;
|
import org.keycloak.events.EventListenerProvider;
|
||||||
import org.keycloak.events.EventListenerProviderFactory;
|
import org.keycloak.events.EventListenerProviderFactory;
|
||||||
|
@ -17,7 +18,9 @@ import io.micrometer.core.instrument.MeterRegistry;
|
||||||
*/
|
*/
|
||||||
public class MicrometerEventListenerFactory implements EventListenerProviderFactory {
|
public class MicrometerEventListenerFactory implements EventListenerProviderFactory {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(MicrometerEventListener.class);
|
||||||
private MeterRegistry registry;
|
private MeterRegistry registry;
|
||||||
|
private boolean replace;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
@ -25,7 +28,10 @@ public class MicrometerEventListenerFactory implements EventListenerProviderFact
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Scope config) {}
|
public void init(Scope config) {
|
||||||
|
replace = "true".equals(System.getenv("KC_METRICS_EVENT_REPLACE_IDS"));
|
||||||
|
log.info(replace ? "Configured with model names." : "Configured with model ids.");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
@ -34,7 +40,7 @@ public class MicrometerEventListenerFactory implements EventListenerProviderFact
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EventListenerProvider create(KeycloakSession session) {
|
public EventListenerProvider create(KeycloakSession session) {
|
||||||
return new MicrometerEventListener(registry);
|
return new MicrometerEventListener(registry, session, replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -28,37 +28,42 @@ public class KeycloakIT {
|
||||||
@Test
|
@Test
|
||||||
void loginAndAttempts(KeycloakClient keycloak, Prometheus prometheus) {
|
void loginAndAttempts(KeycloakClient keycloak, Prometheus prometheus) {
|
||||||
|
|
||||||
|
var clientId1 = UUID.randomUUID().toString();
|
||||||
var realmName1 = UUID.randomUUID().toString();
|
var realmName1 = UUID.randomUUID().toString();
|
||||||
var username1 = UUID.randomUUID().toString();
|
var username1 = UUID.randomUUID().toString();
|
||||||
var password1 = UUID.randomUUID().toString();
|
var password1 = UUID.randomUUID().toString();
|
||||||
|
keycloak.createRealm(realmName1);
|
||||||
|
keycloak.createClient(realmName1, clientId1);
|
||||||
|
keycloak.createUser(realmName1, username1, password1);
|
||||||
|
|
||||||
|
var clientId2 = UUID.randomUUID().toString();
|
||||||
var realmName2 = UUID.randomUUID().toString();
|
var realmName2 = UUID.randomUUID().toString();
|
||||||
var username2 = UUID.randomUUID().toString();
|
var username2 = UUID.randomUUID().toString();
|
||||||
var password2 = UUID.randomUUID().toString();
|
var password2 = UUID.randomUUID().toString();
|
||||||
var realmId1 = keycloak.createRealm(realmName1);
|
keycloak.createRealm(realmName2);
|
||||||
var realmId2 = keycloak.createRealm(realmName2);
|
keycloak.createClient(realmName2, clientId2);
|
||||||
keycloak.createUser(realmName1, username1, password1);
|
|
||||||
keycloak.createUser(realmName2, username2, password2);
|
keycloak.createUser(realmName2, username2, password2);
|
||||||
|
|
||||||
prometheus.scrap();
|
prometheus.scrap();
|
||||||
var loginBefore = prometheus.userEvent(EventType.LOGIN);
|
var loginBefore = prometheus.userEvent(EventType.LOGIN);
|
||||||
var loginBefore1 = prometheus.userEvent(EventType.LOGIN, realmId1);
|
var loginBefore1 = prometheus.userEvent(EventType.LOGIN, realmName1, clientId1);
|
||||||
var loginBefore2 = prometheus.userEvent(EventType.LOGIN, realmId2);
|
var loginBefore2 = prometheus.userEvent(EventType.LOGIN, realmName2, clientId2);
|
||||||
var loginErrorBefore = prometheus.userEvent(EventType.LOGIN_ERROR);
|
var loginErrorBefore = prometheus.userEvent(EventType.LOGIN_ERROR);
|
||||||
var loginErrorBefore1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId1);
|
var loginErrorBefore1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName1, clientId1);
|
||||||
var loginErrorBefore2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId2);
|
var loginErrorBefore2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName2, clientId2);
|
||||||
|
|
||||||
assertTrue(keycloak.login(realmName1, username1, password1));
|
assertTrue(keycloak.login(clientId1, realmName1, username1, password1));
|
||||||
assertTrue(keycloak.login(realmName1, username1, password1));
|
assertTrue(keycloak.login(clientId1, realmName1, username1, password1));
|
||||||
assertTrue(keycloak.login(realmName2, username2, password2));
|
assertTrue(keycloak.login(clientId2, realmName2, username2, password2));
|
||||||
assertFalse(keycloak.login(realmName2, username2, "nope"));
|
assertFalse(keycloak.login(clientId2, realmName2, username2, "nope"));
|
||||||
|
|
||||||
prometheus.scrap();
|
prometheus.scrap();
|
||||||
var loginAfter = prometheus.userEvent(EventType.LOGIN);
|
var loginAfter = prometheus.userEvent(EventType.LOGIN);
|
||||||
var loginAfter1 = prometheus.userEvent(EventType.LOGIN, realmId1);
|
var loginAfter1 = prometheus.userEvent(EventType.LOGIN, realmName1, clientId1);
|
||||||
var loginAfter2 = prometheus.userEvent(EventType.LOGIN, realmId2);
|
var loginAfter2 = prometheus.userEvent(EventType.LOGIN, realmName2, clientId2);
|
||||||
var loginErrorAfter = prometheus.userEvent(EventType.LOGIN_ERROR);
|
var loginErrorAfter = prometheus.userEvent(EventType.LOGIN_ERROR);
|
||||||
var loginErrorAfter1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId1);
|
var loginErrorAfter1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName1, clientId1);
|
||||||
var loginErrorAfter2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId2);
|
var loginErrorAfter2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName2, clientId2);
|
||||||
|
|
||||||
assertAll("prometheus",
|
assertAll("prometheus",
|
||||||
() -> assertEquals(loginBefore + 3, loginAfter, "login success total"),
|
() -> assertEquals(loginBefore + 3, loginAfter, "login success total"),
|
||||||
|
|
|
@ -21,9 +21,12 @@ import org.keycloak.events.EventType;
|
||||||
import org.keycloak.events.admin.AdminEvent;
|
import org.keycloak.events.admin.AdminEvent;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
import org.keycloak.events.admin.ResourceType;
|
import org.keycloak.events.admin.ResourceType;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Captor;
|
import org.mockito.Captor;
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
@ -38,8 +41,14 @@ import io.micrometer.core.instrument.MeterRegistry;
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
public class MicrometerEventListenerTest {
|
public class MicrometerEventListenerTest {
|
||||||
|
|
||||||
@InjectMocks
|
@Mock
|
||||||
MicrometerEventListener listener;
|
KeycloakSession session;
|
||||||
|
@Mock
|
||||||
|
RealmProvider realmProvider;
|
||||||
|
@Mock
|
||||||
|
RealmModel realmModel;
|
||||||
|
@Mock
|
||||||
|
ClientModel clientModel;
|
||||||
@Mock
|
@Mock
|
||||||
MeterRegistry registry;
|
MeterRegistry registry;
|
||||||
@Mock
|
@Mock
|
||||||
|
@ -54,39 +63,91 @@ public class MicrometerEventListenerTest {
|
||||||
when(registry.counter(metricCaptor.capture(), tagsCaptor.capture())).thenReturn(counter);
|
when(registry.counter(metricCaptor.capture(), tagsCaptor.capture())).thenReturn(counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DisplayName("onEvent(Event)")
|
@DisplayName("onEvent(true)")
|
||||||
@Nested
|
@Nested
|
||||||
class onEvent {
|
class onEvent {
|
||||||
|
|
||||||
@DisplayName("without error")
|
@DisplayName("replace(true) - without error")
|
||||||
@Test
|
@Test
|
||||||
void withoutError() {
|
void replaceWithoutError() {
|
||||||
|
|
||||||
|
var realmId = UUID.randomUUID().toString();
|
||||||
|
var realmName = UUID.randomUUID().toString();
|
||||||
|
var clientId = UUID.randomUUID().toString();
|
||||||
|
var clientName = UUID.randomUUID().toString();
|
||||||
|
var type = EventType.LOGIN;
|
||||||
|
|
||||||
|
when(session.realms()).thenReturn(realmProvider);
|
||||||
|
when(realmProvider.getRealm(realmId)).thenReturn(realmModel);
|
||||||
|
when(realmModel.getName()).thenReturn(realmName);
|
||||||
|
when(realmModel.getClientById(clientId)).thenReturn(clientModel);
|
||||||
|
when(clientModel.getClientId()).thenReturn(clientName);
|
||||||
|
|
||||||
|
listener(true).onEvent(toEvent(realmId, clientId, type, null));
|
||||||
|
assertEvent(realmName, clientName, type.toString(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("replace(true) - with error")
|
||||||
|
@Test
|
||||||
|
void replaceWithError() {
|
||||||
|
|
||||||
|
var realmId = UUID.randomUUID().toString();
|
||||||
|
var realmName = UUID.randomUUID().toString();
|
||||||
|
var clientId = UUID.randomUUID().toString();
|
||||||
|
var clientName = UUID.randomUUID().toString();
|
||||||
|
var type = EventType.LOGIN_ERROR;
|
||||||
|
var error = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
when(session.realms()).thenReturn(realmProvider);
|
||||||
|
when(realmProvider.getRealm(realmId)).thenReturn(realmModel);
|
||||||
|
when(realmModel.getName()).thenReturn(realmName);
|
||||||
|
when(realmModel.getClientById(clientId)).thenReturn(clientModel);
|
||||||
|
when(clientModel.getClientId()).thenReturn(clientName);
|
||||||
|
|
||||||
|
listener(true).onEvent(toEvent(realmId, clientId, type, error));
|
||||||
|
assertEvent(realmName, clientName, type.toString(), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("replace(true) - all fields empty")
|
||||||
|
@Test
|
||||||
|
void replaceFieldsEmpty() {
|
||||||
|
|
||||||
|
when(session.realms()).thenReturn(realmProvider);
|
||||||
|
when(realmProvider.getRealm(any())).thenReturn(null);
|
||||||
|
|
||||||
|
listener(true).onEvent(toEvent(null, null, null, null));
|
||||||
|
assertEvent("", "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("replace(false) - without error")
|
||||||
|
@Test
|
||||||
|
void notReplaceWithoutError() {
|
||||||
|
|
||||||
var realmId = UUID.randomUUID().toString();
|
var realmId = UUID.randomUUID().toString();
|
||||||
var clientId = UUID.randomUUID().toString();
|
var clientId = UUID.randomUUID().toString();
|
||||||
var type = EventType.LOGIN;
|
var type = EventType.LOGIN;
|
||||||
|
|
||||||
listener.onEvent(toEvent(realmId, clientId, type, null));
|
listener(false).onEvent(toEvent(realmId, clientId, type, null));
|
||||||
assertEvent(realmId, clientId, type.toString(), "");
|
assertEvent(realmId, clientId, type.toString(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@DisplayName("with error")
|
@DisplayName("replace(false) - with error")
|
||||||
@Test
|
@Test
|
||||||
void withError() {
|
void notReplaceWithError() {
|
||||||
|
|
||||||
var realmId = UUID.randomUUID().toString();
|
var realmId = UUID.randomUUID().toString();
|
||||||
var clientId = UUID.randomUUID().toString();
|
var clientId = UUID.randomUUID().toString();
|
||||||
var type = EventType.LOGIN_ERROR;
|
var type = EventType.LOGIN_ERROR;
|
||||||
var error = UUID.randomUUID().toString();
|
var error = UUID.randomUUID().toString();
|
||||||
|
|
||||||
listener.onEvent(toEvent(realmId, clientId, type, error));
|
listener(false).onEvent(toEvent(realmId, clientId, type, error));
|
||||||
assertEvent(realmId, clientId, type.toString(), error);
|
assertEvent(realmId, clientId, type.toString(), error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DisplayName("all fields empty")
|
@DisplayName("replace(false) - all fields empty")
|
||||||
@Test
|
@Test
|
||||||
void fieldsEmpty() {
|
void notReplaceFieldsEmpty() {
|
||||||
listener.onEvent(toEvent(null, null, null, null));
|
listener(false).onEvent(toEvent(null, null, null, null));
|
||||||
assertEvent("", "", "", "");
|
assertEvent("", "", "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,35 +173,81 @@ public class MicrometerEventListenerTest {
|
||||||
@Nested
|
@Nested
|
||||||
class onAdminEvent {
|
class onAdminEvent {
|
||||||
|
|
||||||
@DisplayName("without error")
|
@DisplayName("replace(true) - without error")
|
||||||
@Test
|
@Test
|
||||||
void withoutError() {
|
void replaceWithoutError() {
|
||||||
|
|
||||||
|
var realmId = UUID.randomUUID().toString();
|
||||||
|
var realmName = UUID.randomUUID().toString();
|
||||||
|
var resource = ResourceType.USER;
|
||||||
|
var operation = OperationType.CREATE;
|
||||||
|
|
||||||
|
when(session.realms()).thenReturn(realmProvider);
|
||||||
|
when(realmProvider.getRealm(realmId)).thenReturn(realmModel);
|
||||||
|
when(realmModel.getName()).thenReturn(realmName);
|
||||||
|
|
||||||
|
listener(true).onEvent(toAdminEvent(realmId, resource, operation, null), false);
|
||||||
|
assertAdminEvent(realmName, resource.toString(), operation.toString(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("replace(true) - with error")
|
||||||
|
@Test
|
||||||
|
void replaceWithError() {
|
||||||
|
|
||||||
|
var realmId = UUID.randomUUID().toString();
|
||||||
|
var realmName = UUID.randomUUID().toString();
|
||||||
|
var resource = ResourceType.USER;
|
||||||
|
var operation = OperationType.CREATE;
|
||||||
|
var error = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
when(session.realms()).thenReturn(realmProvider);
|
||||||
|
when(realmProvider.getRealm(realmId)).thenReturn(realmModel);
|
||||||
|
when(realmModel.getName()).thenReturn(realmName);
|
||||||
|
|
||||||
|
listener(true).onEvent(toAdminEvent(realmId, resource, operation, error), false);
|
||||||
|
assertAdminEvent(realmName, resource.toString(), operation.toString(), error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("replace(true) - all fields empty")
|
||||||
|
@Test
|
||||||
|
void replaceFieldsEmpty() {
|
||||||
|
|
||||||
|
when(session.realms()).thenReturn(realmProvider);
|
||||||
|
when(realmProvider.getRealm(any())).thenReturn(null);
|
||||||
|
|
||||||
|
listener(true).onEvent(toAdminEvent(null, null, null, null), false);
|
||||||
|
assertAdminEvent("", "", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DisplayName("replace(false) - without error")
|
||||||
|
@Test
|
||||||
|
void noReplaceWithoutError() {
|
||||||
|
|
||||||
var realmId = UUID.randomUUID().toString();
|
var realmId = UUID.randomUUID().toString();
|
||||||
var resource = ResourceType.USER;
|
var resource = ResourceType.USER;
|
||||||
var operation = OperationType.CREATE;
|
var operation = OperationType.CREATE;
|
||||||
|
|
||||||
listener.onEvent(toAdminEvent(realmId, resource, operation, null), false);
|
listener(false).onEvent(toAdminEvent(realmId, resource, operation, null), false);
|
||||||
assertAdminEvent(realmId, resource.toString(), operation.toString(), "");
|
assertAdminEvent(realmId, resource.toString(), operation.toString(), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@DisplayName("with error")
|
@DisplayName("replace(false) - with error")
|
||||||
@Test
|
@Test
|
||||||
void withError() {
|
void noReplaceWithError() {
|
||||||
|
|
||||||
var realmId = UUID.randomUUID().toString();
|
var realmId = UUID.randomUUID().toString();
|
||||||
var resource = ResourceType.USER;
|
var resource = ResourceType.USER;
|
||||||
var operation = OperationType.CREATE;
|
var operation = OperationType.CREATE;
|
||||||
var error = UUID.randomUUID().toString();
|
var error = UUID.randomUUID().toString();
|
||||||
|
|
||||||
listener.onEvent(toAdminEvent(realmId, resource, operation, error), false);
|
listener(false).onEvent(toAdminEvent(realmId, resource, operation, error), false);
|
||||||
assertAdminEvent(realmId, resource.toString(), operation.toString(), error);
|
assertAdminEvent(realmId, resource.toString(), operation.toString(), error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DisplayName("all fields empty")
|
@DisplayName("replace(false) - all fields empty")
|
||||||
@Test
|
@Test
|
||||||
void fieldsEmpty() {
|
void noReplaceFieldsEmpty() {
|
||||||
listener.onEvent(toAdminEvent(null, null, null, null), false);
|
listener(false).onEvent(toAdminEvent(null, null, null, null), false);
|
||||||
assertAdminEvent("", "", "", "");
|
assertAdminEvent("", "", "", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,6 +269,10 @@ public class MicrometerEventListenerTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MicrometerEventListener listener(boolean replace) {
|
||||||
|
return new MicrometerEventListener(registry, session, replace);
|
||||||
|
}
|
||||||
|
|
||||||
private void assertCounter(String metric, Map<String, String> tags) {
|
private void assertCounter(String metric, Map<String, String> tags) {
|
||||||
verify(registry).counter(anyString(), any(String[].class));
|
verify(registry).counter(anyString(), any(String[].class));
|
||||||
verify(counter).increment();
|
verify(counter).increment();
|
||||||
|
|
|
@ -29,18 +29,20 @@ public class KeycloakClient {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String createRealm(String realmName) {
|
public void createRealm(String realmName) {
|
||||||
var client = new ClientRepresentation();
|
|
||||||
client.setClientId("test");
|
|
||||||
client.setPublicClient(true);
|
|
||||||
client.setDirectAccessGrantsEnabled(true);
|
|
||||||
var realm = new RealmRepresentation();
|
var realm = new RealmRepresentation();
|
||||||
realm.setEnabled(true);
|
realm.setEnabled(true);
|
||||||
realm.setRealm(realmName);
|
realm.setRealm(realmName);
|
||||||
realm.setEventsListeners(List.of("metrics-listener"));
|
realm.setEventsListeners(List.of("metrics-listener"));
|
||||||
realm.setClients(List.of(client));
|
|
||||||
keycloak.realms().create(realm);
|
keycloak.realms().create(realm);
|
||||||
return keycloak.realms().realm(realmName).toRepresentation().getId();
|
}
|
||||||
|
|
||||||
|
public void createClient(String realmName, String clientId) {
|
||||||
|
var client = new ClientRepresentation();
|
||||||
|
client.setClientId(clientId);
|
||||||
|
client.setPublicClient(true);
|
||||||
|
client.setDirectAccessGrantsEnabled(true);
|
||||||
|
keycloak.realms().realm(realmName).clients().create(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void createUser(String realmName, String username, String password) {
|
public void createUser(String realmName, String username, String password) {
|
||||||
|
@ -57,10 +59,10 @@ public class KeycloakClient {
|
||||||
keycloak.realms().realm(realmName).users().create(user);
|
keycloak.realms().realm(realmName).users().create(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean login(String realmName, String username, String password) {
|
public boolean login(String clientId, String realmName, String username, String password) {
|
||||||
try {
|
try {
|
||||||
token.grantToken(realmName, new MultivaluedHashMap<>(Map.of(
|
token.grantToken(realmName, new MultivaluedHashMap<>(Map.of(
|
||||||
OAuth2Constants.CLIENT_ID, "test",
|
OAuth2Constants.CLIENT_ID, clientId,
|
||||||
OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD,
|
OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD,
|
||||||
OAuth2Constants.USERNAME, username,
|
OAuth2Constants.USERNAME, username,
|
||||||
OAuth2Constants.PASSWORD, password)));
|
OAuth2Constants.PASSWORD, password)));
|
||||||
|
|
|
@ -60,6 +60,7 @@ public class KeycloakExtension implements BeforeAllCallback, ParameterResolver {
|
||||||
.withEnv("KC_LOG_CONSOLE_COLOR", "true")
|
.withEnv("KC_LOG_CONSOLE_COLOR", "true")
|
||||||
.withEnv("KC_HEALTH_ENABLED", "true")
|
.withEnv("KC_HEALTH_ENABLED", "true")
|
||||||
.withEnv("KC_METRICS_ENABLED", "true")
|
.withEnv("KC_METRICS_ENABLED", "true")
|
||||||
|
.withEnv("KC_METRICS_EVENT_REPLACE_IDS", "true")
|
||||||
.withCopyFileToContainer(MountableFile.forHostPath(jar), "/opt/keycloak/providers/metrics.jar")
|
.withCopyFileToContainer(MountableFile.forHostPath(jar), "/opt/keycloak/providers/metrics.jar")
|
||||||
.withLogConsumer(out -> System.out.print(out.getUtf8String()))
|
.withLogConsumer(out -> System.out.print(out.getUtf8String()))
|
||||||
.withExposedPorts(8080)
|
.withExposedPorts(8080)
|
||||||
|
|
|
@ -31,11 +31,12 @@ public class Prometheus {
|
||||||
.sum();
|
.sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int userEvent(EventType type, String realmName) {
|
public int userEvent(EventType type, String realmName, String clientId) {
|
||||||
return state.stream()
|
return state.stream()
|
||||||
.filter(metric -> Objects.equals(metric.name(), "keycloak_event_user_total"))
|
.filter(metric -> Objects.equals(metric.name(), "keycloak_event_user_total"))
|
||||||
.filter(metric -> Objects.equals(metric.tags().get("type"), type.toString()))
|
.filter(metric -> Objects.equals(metric.tags().get("type"), type.toString()))
|
||||||
.filter(metric -> Objects.equals(metric.tags().get("realm"), realmName))
|
.filter(metric -> Objects.equals(metric.tags().get("realm"), realmName))
|
||||||
|
.filter(metric -> Objects.equals(metric.tags().get("client"), clientId))
|
||||||
.mapToInt(metric -> metric.value().intValue())
|
.mapToInt(metric -> metric.value().intValue())
|
||||||
.sum();
|
.sum();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue