Initial commit
Adapted from https://github.com/dasniko/keycloak-extensions-demo
This commit is contained in:
commit
0681839596
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
result
|
17
README.md
Executable file
17
README.md
Executable file
|
@ -0,0 +1,17 @@
|
|||
# Keycloak Event Listener
|
||||
|
||||
Some demo event listeners for Keycloak.
|
||||
|
||||
## Highlander Session Restrictor
|
||||
|
||||
Allowing only the last session to survive, if a user logs in on multiple browsers/devices.
|
||||
|
||||
I call it the _Highlander_ mode - _there must only be one!_
|
||||
|
||||
## AWS SNS
|
||||
|
||||
Simply pushing all events to an AWS SNS topic.
|
||||
|
||||
## Last Login Time
|
||||
|
||||
Save the last (most recent) login time in an attribute of the user.
|
133
flake.lock
Normal file
133
flake.lock
Normal file
|
@ -0,0 +1,133 @@
|
|||
{
|
||||
"nodes": {
|
||||
"devshell": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1713532798,
|
||||
"narHash": "sha256-wtBhsdMJA3Wa32Wtm1eeo84GejtI43pMrFrmwLXrsEc=",
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"rev": "12e914740a25ea1891ec619bb53cf5e6ca922e40",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "devshell",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712014858,
|
||||
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701680307,
|
||||
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1704161960,
|
||||
"narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "63143ac2c9186be6d9da6035fa22620018c85932",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1711703276,
|
||||
"narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d8fe5e6c92d0d190646fb9f1056741a229980089",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1714076141,
|
||||
"narHash": "sha256-Drmja/f5MRHZCskS6mvzFqxEaZMeciScCTFxWVLqWEY=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7bb2ccd8cdc44c91edba16c48d2c8f331fb3d856",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devshell": "devshell",
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
44
flake.nix
Normal file
44
flake.nix
Normal file
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
description = "keycloak-event-listener";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
devshell.url = "github:numtide/devshell";
|
||||
};
|
||||
|
||||
outputs = inputs:
|
||||
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [
|
||||
inputs.devshell.flakeModule
|
||||
];
|
||||
|
||||
systems = [
|
||||
"x86_64-linux"
|
||||
];
|
||||
|
||||
perSystem = args@{ system, pkgs, lib, config, ... }: let
|
||||
keycloak-event-listener = pkgs.maven.buildMavenPackage {
|
||||
pname = "keycloak-event-listener";
|
||||
version = "0.0.1";
|
||||
src = ./.;
|
||||
mvnHash = "sha256-tJgqe1WbVodEoRrDFPyHxsFkHIWHAPp5a2WsvWPb2l8=";
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
install -Dm444 -t "$out" target/pubsolar.keycloak-event-listener.jar
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in {
|
||||
packages.default = keycloak-event-listener;
|
||||
packages.keycloak-event-listener = keycloak-event-listener;
|
||||
|
||||
devshells.default = {
|
||||
packages = with pkgs; [
|
||||
maven
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
83
pom.xml
Normal file
83
pom.xml
Normal file
|
@ -0,0 +1,83 @@
|
|||
<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>
|
||||
|
||||
<artifactId>event-listener</artifactId>
|
||||
<version>0.0.1</version>
|
||||
<groupId>pubsolar.keycloak</groupId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-core</artifactId>
|
||||
<version>23.0.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
<version>23.0.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi-private</artifactId>
|
||||
<version>23.0.6</version>
|
||||
</dependency>
|
||||
<!--dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-storage-private</artifactId>
|
||||
<version>23.0.6</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>
|
||||
|
||||
<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>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,114 @@
|
|||
package pubsolar.keycloak.events;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.EventListenerProvider;
|
||||
import org.keycloak.events.EventListenerTransaction;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j(topic = "org.keycloak.events")
|
||||
public class JsonEventListenerProvider implements EventListenerProvider {
|
||||
|
||||
private final KeycloakSession session;
|
||||
private final ObjectMapper mapper;
|
||||
private final Level successLevel;
|
||||
private final Level errorLevel;
|
||||
private final EventListenerTransaction tx = new EventListenerTransaction(this::sendAdminEvent, this::logEvent);
|
||||
|
||||
public JsonEventListenerProvider(KeycloakSession session, ObjectMapper mapper, Level successLevel, Level errorLevel) {
|
||||
this.session = session;
|
||||
this.mapper = mapper;
|
||||
this.successLevel = successLevel;
|
||||
this.errorLevel = errorLevel;
|
||||
|
||||
session.getTransactionManager().enlistAfterCompletion(tx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
tx.addEvent(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(AdminEvent event, boolean includeRepresentation) {
|
||||
tx.addAdminEvent(event, includeRepresentation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
private void logEvent(Event event) {
|
||||
Level level = event.getError() != null ? errorLevel : successLevel;
|
||||
|
||||
if (log.isEnabledForLevel(level)) {
|
||||
String s = null;
|
||||
try {
|
||||
Map<String, Object> map = mapper.convertValue(event, new TypeReference<>() {});
|
||||
|
||||
AuthenticationSessionModel authSession = session.getContext().getAuthenticationSession();
|
||||
if(authSession!=null) {
|
||||
map.put("authSessionParentId", authSession.getParentSession().getId());
|
||||
map.put("authSessionTabId", authSession.getTabId());
|
||||
}
|
||||
|
||||
if (log.isTraceEnabled()) {
|
||||
setKeycloakContext(map);
|
||||
}
|
||||
|
||||
s = mapper.writeValueAsString(map);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Error while trying to JSONify event %s".formatted(ToStringBuilder.reflectionToString(event)), e);
|
||||
}
|
||||
|
||||
log.atLevel(log.isTraceEnabled() ? Level.TRACE : level).log(s);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendAdminEvent(AdminEvent event, boolean includeRepresentation) {
|
||||
Level level = event.getError() != null ? errorLevel : successLevel;
|
||||
|
||||
if (log.isEnabledForLevel(level)) {
|
||||
String s = null;
|
||||
try {
|
||||
Map<String, Object> map = mapper.convertValue(event, new TypeReference<>() {
|
||||
});
|
||||
|
||||
if (log.isTraceEnabled()) {
|
||||
setKeycloakContext(map);
|
||||
}
|
||||
|
||||
s = mapper.writeValueAsString(map);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error("Error while trying to JSONify admin event %s".formatted(ToStringBuilder.reflectionToString(event)), e);
|
||||
}
|
||||
|
||||
log.atLevel(log.isTraceEnabled() ? Level.TRACE : level).log(s);
|
||||
}
|
||||
}
|
||||
|
||||
private void setKeycloakContext(Map<String, Object> map) {
|
||||
KeycloakContext context = session.getContext();
|
||||
UriInfo uriInfo = context.getUri();
|
||||
if (uriInfo != null) {
|
||||
map.put("requestUri", uriInfo.getRequestUri().toString());
|
||||
}
|
||||
HttpHeaders headers = context.getRequestHeaders();
|
||||
if (headers != null) {
|
||||
map.put("cookies", headers.getCookies());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package pubsolar.keycloak.events;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
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;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@AutoService(EventListenerProviderFactory.class)
|
||||
public class JsonEventListenerProviderFactory implements EventListenerProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = "json-logging";
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private Level successLevel;
|
||||
private Level errorLevel;
|
||||
|
||||
@Override
|
||||
public EventListenerProvider create(KeycloakSession keycloakSession) {
|
||||
return new JsonEventListenerProvider(keycloakSession, mapper, successLevel, errorLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
successLevel = Level.valueOf(config.get("success-level", "debug").toUpperCase());
|
||||
errorLevel = Level.valueOf(config.get("error-level", "warn").toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ProviderConfigProperty> getConfigMetadata() {
|
||||
String[] logLevels = Arrays.stream(Level.values())
|
||||
.map(Level::name)
|
||||
.map(String::toLowerCase)
|
||||
.sorted(Comparator.naturalOrder())
|
||||
.toArray(String[]::new);
|
||||
return ProviderConfigurationBuilder.create()
|
||||
.property()
|
||||
.name("success-level")
|
||||
.type("string")
|
||||
.helpText("The log level for success messages.")
|
||||
.options(logLevels)
|
||||
.defaultValue("debug")
|
||||
.add()
|
||||
.property()
|
||||
.name("error-level")
|
||||
.type("string")
|
||||
.helpText("The log level for error messages.")
|
||||
.options(logLevels)
|
||||
.defaultValue("warn")
|
||||
.add()
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package dasniko.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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package pubsolar.keycloak.events;
|
||||
|
||||
import pubsolar.testcontainers.keycloak.KeycloakContainer;
|
||||
import de.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);
|
||||
}
|
||||
|
||||
}
|
28
src/test/resources/demo-realm.json
Normal file
28
src/test/resources/demo-realm.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"id" : "demo",
|
||||
"realm" : "demo",
|
||||
"enabled" : true,
|
||||
"users" : [ {
|
||||
"id" : "49fcf95e-6fb3-4430-a29a-506a8b20e77c",
|
||||
"createdTimestamp" : 1673444664000,
|
||||
"username" : "test",
|
||||
"enabled" : true,
|
||||
"totp" : false,
|
||||
"emailVerified" : true,
|
||||
"firstName" : "Theo",
|
||||
"lastName" : "Tester",
|
||||
"email" : "test@keycloak.de",
|
||||
"credentials" : [ {
|
||||
"id" : "b9fbc30d-4269-49cc-a0ea-9170dc44a30c",
|
||||
"type" : "password",
|
||||
"createdDate" : 1673444664000,
|
||||
"secretData" : "{\"value\":\"yxEZKVTeZlKufE5q4v0Hvxlggg2EaRta5zBtIMxialgwOHrQ3h4Hmre//uk9SlrEv2eqo4aH4bFgPDoktOTyHQ==\",\"salt\":\"d3mk1F43bvQrbV1D+jC1NQ==\",\"additionalParameters\":{}}",
|
||||
"credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
|
||||
} ],
|
||||
"disableableCredentialTypes" : [ ],
|
||||
"requiredActions" : [ ],
|
||||
"realmRoles" : [ "default-roles-demo" ],
|
||||
"notBefore" : 0,
|
||||
"groups" : [ ]
|
||||
} ]
|
||||
}
|
4
src/test/resources/log4j.properties
Normal file
4
src/test/resources/log4j.properties
Normal file
|
@ -0,0 +1,4 @@
|
|||
log4j.rootLogger=INFO, stdout
|
||||
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
|
||||
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
|
||||
log4j.appender.stdout.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
|
Loading…
Reference in a new issue