Add option to replace model ids with model names, fix #3 (#6)

This commit is contained in:
Stephan Schnabel 2023-03-10 13:31:06 +01:00 committed by GitHub
parent 62bdac4717
commit db929056f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 225 additions and 53 deletions

View file

@ -28,37 +28,42 @@ public class KeycloakIT {
@Test
void loginAndAttempts(KeycloakClient keycloak, Prometheus prometheus) {
var clientId1 = UUID.randomUUID().toString();
var realmName1 = UUID.randomUUID().toString();
var username1 = 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 username2 = UUID.randomUUID().toString();
var password2 = UUID.randomUUID().toString();
var realmId1 = keycloak.createRealm(realmName1);
var realmId2 = keycloak.createRealm(realmName2);
keycloak.createUser(realmName1, username1, password1);
keycloak.createRealm(realmName2);
keycloak.createClient(realmName2, clientId2);
keycloak.createUser(realmName2, username2, password2);
prometheus.scrap();
var loginBefore = prometheus.userEvent(EventType.LOGIN);
var loginBefore1 = prometheus.userEvent(EventType.LOGIN, realmId1);
var loginBefore2 = prometheus.userEvent(EventType.LOGIN, realmId2);
var loginBefore1 = prometheus.userEvent(EventType.LOGIN, realmName1, clientId1);
var loginBefore2 = prometheus.userEvent(EventType.LOGIN, realmName2, clientId2);
var loginErrorBefore = prometheus.userEvent(EventType.LOGIN_ERROR);
var loginErrorBefore1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId1);
var loginErrorBefore2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId2);
var loginErrorBefore1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName1, clientId1);
var loginErrorBefore2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName2, clientId2);
assertTrue(keycloak.login(realmName1, username1, password1));
assertTrue(keycloak.login(realmName1, username1, password1));
assertTrue(keycloak.login(realmName2, username2, password2));
assertFalse(keycloak.login(realmName2, username2, "nope"));
assertTrue(keycloak.login(clientId1, realmName1, username1, password1));
assertTrue(keycloak.login(clientId1, realmName1, username1, password1));
assertTrue(keycloak.login(clientId2, realmName2, username2, password2));
assertFalse(keycloak.login(clientId2, realmName2, username2, "nope"));
prometheus.scrap();
var loginAfter = prometheus.userEvent(EventType.LOGIN);
var loginAfter1 = prometheus.userEvent(EventType.LOGIN, realmId1);
var loginAfter2 = prometheus.userEvent(EventType.LOGIN, realmId2);
var loginAfter1 = prometheus.userEvent(EventType.LOGIN, realmName1, clientId1);
var loginAfter2 = prometheus.userEvent(EventType.LOGIN, realmName2, clientId2);
var loginErrorAfter = prometheus.userEvent(EventType.LOGIN_ERROR);
var loginErrorAfter1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId1);
var loginErrorAfter2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmId2);
var loginErrorAfter1 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName1, clientId1);
var loginErrorAfter2 = prometheus.userEvent(EventType.LOGIN_ERROR, realmName2, clientId2);
assertAll("prometheus",
() -> assertEquals(loginBefore + 3, loginAfter, "login success total"),

View file

@ -21,9 +21,12 @@ import org.keycloak.events.EventType;
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.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ -38,8 +41,14 @@ import io.micrometer.core.instrument.MeterRegistry;
@ExtendWith(MockitoExtension.class)
public class MicrometerEventListenerTest {
@InjectMocks
MicrometerEventListener listener;
@Mock
KeycloakSession session;
@Mock
RealmProvider realmProvider;
@Mock
RealmModel realmModel;
@Mock
ClientModel clientModel;
@Mock
MeterRegistry registry;
@Mock
@ -54,39 +63,91 @@ public class MicrometerEventListenerTest {
when(registry.counter(metricCaptor.capture(), tagsCaptor.capture())).thenReturn(counter);
}
@DisplayName("onEvent(Event)")
@DisplayName("onEvent(true)")
@Nested
class onEvent {
@DisplayName("without error")
@DisplayName("replace(true) - without error")
@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 clientId = UUID.randomUUID().toString();
var type = EventType.LOGIN;
listener.onEvent(toEvent(realmId, clientId, type, null));
listener(false).onEvent(toEvent(realmId, clientId, type, null));
assertEvent(realmId, clientId, type.toString(), "");
}
@DisplayName("with error")
@DisplayName("replace(false) - with error")
@Test
void withError() {
void notReplaceWithError() {
var realmId = UUID.randomUUID().toString();
var clientId = UUID.randomUUID().toString();
var type = EventType.LOGIN_ERROR;
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);
}
@DisplayName("all fields empty")
@DisplayName("replace(false) - all fields empty")
@Test
void fieldsEmpty() {
listener.onEvent(toEvent(null, null, null, null));
void notReplaceFieldsEmpty() {
listener(false).onEvent(toEvent(null, null, null, null));
assertEvent("", "", "", "");
}
@ -112,35 +173,81 @@ public class MicrometerEventListenerTest {
@Nested
class onAdminEvent {
@DisplayName("without error")
@DisplayName("replace(true) - without error")
@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 resource = ResourceType.USER;
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(), "");
}
@DisplayName("with error")
@DisplayName("replace(false) - with error")
@Test
void withError() {
void noReplaceWithError() {
var realmId = UUID.randomUUID().toString();
var resource = ResourceType.USER;
var operation = OperationType.CREATE;
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);
}
@DisplayName("all fields empty")
@DisplayName("replace(false) - all fields empty")
@Test
void fieldsEmpty() {
listener.onEvent(toAdminEvent(null, null, null, null), false);
void noReplaceFieldsEmpty() {
listener(false).onEvent(toAdminEvent(null, null, null, null), false);
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) {
verify(registry).counter(anyString(), any(String[].class));
verify(counter).increment();

View file

@ -29,18 +29,20 @@ public class KeycloakClient {
this.token = token;
}
public String createRealm(String realmName) {
var client = new ClientRepresentation();
client.setClientId("test");
client.setPublicClient(true);
client.setDirectAccessGrantsEnabled(true);
public void createRealm(String realmName) {
var realm = new RealmRepresentation();
realm.setEnabled(true);
realm.setRealm(realmName);
realm.setEventsListeners(List.of("metrics-listener"));
realm.setClients(List.of(client));
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) {
@ -57,10 +59,10 @@ public class KeycloakClient {
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 {
token.grantToken(realmName, new MultivaluedHashMap<>(Map.of(
OAuth2Constants.CLIENT_ID, "test",
OAuth2Constants.CLIENT_ID, clientId,
OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD,
OAuth2Constants.USERNAME, username,
OAuth2Constants.PASSWORD, password)));

View file

@ -60,6 +60,7 @@ public class KeycloakExtension implements BeforeAllCallback, ParameterResolver {
.withEnv("KC_LOG_CONSOLE_COLOR", "true")
.withEnv("KC_HEALTH_ENABLED", "true")
.withEnv("KC_METRICS_ENABLED", "true")
.withEnv("KC_METRICS_EVENT_REPLACE_IDS", "true")
.withCopyFileToContainer(MountableFile.forHostPath(jar), "/opt/keycloak/providers/metrics.jar")
.withLogConsumer(out -> System.out.print(out.getUtf8String()))
.withExposedPorts(8080)

View file

@ -31,11 +31,12 @@ public class Prometheus {
.sum();
}
public int userEvent(EventType type, String realmName) {
public int userEvent(EventType type, String realmName, String clientId) {
return state.stream()
.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("realm"), realmName))
.filter(metric -> Objects.equals(metric.tags().get("client"), clientId))
.mapToInt(metric -> metric.value().intValue())
.sum();
}