chore: Update to keycloak 25.0.6, rename namespace

Code taken from https://github.com/ThoreKr/keycloak-last-login-event-listener

Has no license in repo, the author doubts it is original enough to copyright: https://github.com/ThoreKr/keycloak-last-login-event-listener/issues/7
This commit is contained in:
b12f 2025-01-25 13:42:22 +01:00
parent f2a3da5f26
commit 1bc1b1f904
Signed by: b12f
GPG key ID: 729956E1124F8F26
8 changed files with 229 additions and 208 deletions

View file

@ -22,11 +22,11 @@
pname = "keycloak-event-listener";
version = "0.0.1";
src = ./.;
mvnHash = "sha256-SSiiKgrNUV2T84Vj4zhKzvl7HAZlPZ2OwUzoIsureu8=";
mvnHash = "sha256-WNNfJAWUqY+mUCrwPNfBw/El19EZkcW/YOOJ4VoCvLA=";
installPhase = ''
runHook preInstall
install -Dm444 -t "$out" target/pubsolar.keycloak-event-listener.jar
install -Dm444 -t "$out" target/solar.pub.keycloak-event-listener.jar
runHook postInstall
'';
};

151
pom.xml
View file

@ -1,83 +1,82 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>event-listener</artifactId>
<version>0.0.1</version>
<groupId>pubsolar.keycloak</groupId>
<artifactId>event-listener</artifactId>
<version>0.0.1</version>
<groupId>solar.pub.keycloak</groupId>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>24.0.5</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>24.0.5</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<version>24.0.5</version>
</dependency>
<!--dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-storage-private</artifactId>
<version>24.0.5</version>
</dependency-->
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>25.0.6</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
<version>25.0.6</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi-private</artifactId>
<version>25.0.6</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.7</version>
</dependency>
<dependency>
<groupId>com.github.dasniko</groupId>
<artifactId>testcontainers-keycloak</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.5.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
<version>2.0.13</version>
</dependency>
</dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
<build>
<finalName>${project.groupId}-${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.7</version>
</dependency>
<dependency>
<groupId>com.github.dasniko</groupId>
<artifactId>testcontainers-keycloak</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>5.4.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-reload4j</artifactId>
<version>2.0.13</version>
</dependency>
</dependencies>
<build>
<finalName>${project.groupId}-${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,53 +0,0 @@
package pubsolar.keycloak.events;
import dasniko.testcontainers.keycloak.KeycloakContainer;
import dasniko.keycloak.test.TestBase;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@Testcontainers
public class LastLoginTimeListenerTest extends TestBase {
private static final String REALM = "demo";
@Container
private static final KeycloakContainer keycloak = new KeycloakContainer()
.withRealmImportFile("demo-realm.json")
.withEnv("KC_SPI_EVENTS_LISTENER_LAST_LOGIN_TIME_ATTRIBUTE_NAME", "lastLogin")
.withProviderClassesFrom("target/classes");
@Test
public void testLastLoginTime() {
Keycloak admin = keycloak.getKeycloakAdminClient();
// check user has no attributes
List<UserRepresentation> users = admin.realm(REALM).users().searchByUsername("test", true);
UserRepresentation testUser = users.get(0);
Map<String, List<String>> attributes = testUser.getAttributes();
assertNull(attributes);
// configure custom events listener
RealmEventsConfigRepresentation eventsConfig = new RealmEventsConfigRepresentation();
eventsConfig.setEventsListeners(List.of(LastLoginTimeListenerFactory.PROVIDER_ID));
admin.realm(REALM).updateRealmEventsConfig(eventsConfig);
// "login" user
requestToken(keycloak, REALM, "test", "test");
// check user has last-login-time attribute
testUser = admin.realm(REALM).users().searchByUsername("test", true).get(0);
String lastLoginTime = testUser.firstAttribute("lastLogin");
assertNotNull(lastLoginTime);
}
}

View file

@ -0,0 +1,53 @@
package solar.pub.keycloak.events.logging;
import dasniko.testcontainers.keycloak.KeycloakContainer;
import dasniko.keycloak.test.TestBase;
import org.junit.jupiter.api.Test;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@Testcontainers
public class LastLoginTimeListenerTest extends TestBase {
private static final String REALM = "demo";
@Container
private static final KeycloakContainer keycloak = new KeycloakContainer()
.withRealmImportFile("demo-realm.json")
.withEnv("KC_SPI_EVENTS_LISTENER_LAST_LOGIN_TIME_ATTRIBUTE_NAME", "last-login")
.withProviderClassesFrom("target/classes");
@Test
public void testLastLoginTime() {
Keycloak admin = keycloak.getKeycloakAdminClient();
// check user has no attributes
List<UserRepresentation> users = admin.realm(REALM).users().searchByUsername("test", true);
UserRepresentation testUser = users.get(0);
Map<String, List<String>> attributes = testUser.getAttributes();
assertNull(attributes);
// configure custom events listener
RealmEventsConfigRepresentation eventsConfig = new RealmEventsConfigRepresentation();
eventsConfig.setEventsListeners(List.of(LastLoginTimeListenerFactory.PROVIDER_ID));
admin.realm(REALM).updateRealmEventsConfig(eventsConfig);
// "login" user
requestToken(keycloak, REALM, "test", "test");
// check user has last-login-time attribute
testUser = admin.realm(REALM).users().searchByUsername("test", true).get(0);
String lastLoginTime = testUser.firstAttribute("last-login");
assertNotNull(lastLoginTime);
}
}

View file

@ -1,38 +0,0 @@
package pubsolar.keycloak.events;
import lombok.RequiredArgsConstructor;
import org.keycloak.common.util.Time;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserModel;
/**
* @author Niko Köbler, https://www.n-k.de, @dasniko
*/
@RequiredArgsConstructor
public class LastLoginTimeListener implements EventListenerProvider {
private final KeycloakSession session;
@Override
public void onEvent(Event event) {
if (event.getType().equals(EventType.LOGIN)) {
UserModel user = session.users().getUserById(session.getContext().getRealm(), event.getUserId());
if (user != null) {
user.setSingleAttribute(LastLoginTimeListenerFactory.attributeName, Integer.toString(Time.currentTime()));
}
}
}
@Override
public void onEvent(AdminEvent event, boolean includeRepresentation) {
}
@Override
public void close() {
}
}

View file

@ -1,39 +0,0 @@
package pubsolar.keycloak.events;
import com.google.auto.service.AutoService;
import org.keycloak.Config;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@AutoService(EventListenerProviderFactory.class)
public class LastLoginTimeListenerFactory implements EventListenerProviderFactory {
public static final String PROVIDER_ID = "last-login-time";
static String attributeName;
@Override
public EventListenerProvider create(KeycloakSession session) {
return new LastLoginTimeListener(session);
}
@Override
public void init(Config.Scope config) {
attributeName = config.get("attribute-name", "lastLoginTime");
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
}

View file

@ -0,0 +1,63 @@
package solar.pub.keycloak.events.logging;
import org.jboss.logging.Logger;
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmProvider;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class LastLoginEventListenerProvider implements EventListenerProvider {
private static final Logger log = Logger.getLogger(LastLoginEventListenerProvider.class);
private final KeycloakSession session;
private final RealmProvider model;
public LastLoginEventListenerProvider(KeycloakSession session) {
this.session = session;
this.model = session.realms();
}
@Override
public void onEvent(Event event) {
if (!EventType.LOGIN.equals(event.getType())) {
return;
}
var realm = model.getRealm(event.getRealmId());
var user = session.users().getUserById(realm, event.getUserId());
if (user == null) {
return;
}
log.info("Updating last login status for user: " + user.getUsername());
var userAttrs = user.getAttributes();
if (userAttrs.containsKey("last-login")) {
var userLastLogin = userAttrs.get("last-login");
if (userLastLogin != null && !userLastLogin.isEmpty()) {
user.setSingleAttribute("prior-login", userLastLogin.get(0));
}
}
// Use current server time for login event
var loginTime = ZonedDateTime.now(ZoneOffset.UTC);
var loginTimeS = DateTimeFormatter.ISO_DATE_TIME.format(loginTime);
user.setSingleAttribute("last-login", loginTimeS);
}
@Override
public void onEvent(AdminEvent adminEvent, boolean b) {
}
@Override
public void close() {
// Nothing to close
}
}

View file

@ -0,0 +1,36 @@
package solar.pub.keycloak.events.logging;
import org.keycloak.Config;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
public class LastLoginEventListenerProviderFactory implements EventListenerProviderFactory {
@Override
public LastLoginEventListenerProvider create(KeycloakSession keycloakSession) {
return new LastLoginEventListenerProvider(keycloakSession);
}
@Override
public void init(Config.Scope scope) {
//
}
@Override
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
//
}
@Override
public void close() {
//
}
@Override
public String getId() {
return "last_login";
}
}