Merge pull request #408 from stianst/email-events

Email audit listener
This commit is contained in:
Stian Thorgersen 2014-05-20 17:29:45 +01:00
commit 1b4b537043
53 changed files with 659 additions and 255 deletions

View file

@ -75,7 +75,7 @@ public class Audit {
return this;
}
public Audit event(String e) {
public Audit event(EventType e) {
event.setEvent(e);
return this;
}
@ -108,6 +108,7 @@ public class Audit {
}
public void error(String error) {
event.setEvent(EventType.valueOf(event.getEvent().name() + "_ERROR"));
event.setError(error);
send();
}

View file

@ -10,7 +10,7 @@ public class Event {
private long time;
private String event;
private EventType event;
private String realmId;
@ -34,11 +34,11 @@ public class Event {
this.time = time;
}
public String getEvent() {
public EventType getEvent() {
return event;
}
public void setEvent(String event) {
public void setEvent(EventType event) {
this.event = event;
}

View file

@ -7,7 +7,7 @@ import java.util.List;
*/
public interface EventQuery {
public EventQuery event(String... events);
public EventQuery event(EventType... events);
public EventQuery realm(String realmId);

View file

@ -0,0 +1,42 @@
package org.keycloak.audit;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public enum EventType {
LOGIN,
LOGIN_ERROR,
REGISTER,
REGISTER_ERROR,
LOGOUT,
LOGOUT_ERROR,
CODE_TO_TOKEN,
CODE_TO_TOKEN_ERROR,
REFRESH_TOKEN,
REFRESH_TOKEN_ERROR,
SOCIAL_LINK,
SOCIAL_LINK_ERROR,
REMOVE_SOCIAL_LINK,
REMOVE_SOCIAL_LINK_ERROR,
UPDATE_EMAIL,
UPDATE_EMAIL_ERROR,
UPDATE_PROFILE,
UPDATE_PROFILE_ERROR,
UPDATE_PASSWORD,
UPDATE_PASSWORD_ERROR,
UPDATE_TOTP,
UPDATE_TOTP_ERROR,
VERIFY_EMAIL,
VERIFY_EMAIL_ERROR,
REMOVE_TOTP,
REMOVE_TOTP_ERROR,
SEND_VERIFY_EMAIL,
SEND_VERIFY_EMAIL_ERROR,
SEND_RESET_PASSWORD,
SEND_RESET_PASSWORD_ERROR
}

View file

@ -1,29 +0,0 @@
package org.keycloak.audit;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface Events {
String LOGIN = "login";
String REGISTER = "register";
String LOGOUT = "logout";
String CODE_TO_TOKEN = "code_to_token";
String REFRESH_TOKEN = "refresh_token";
String SOCIAL_LINK = "social_link";
String REMOVE_SOCIAL_LINK = "remove_social_link";
String UPDATE_EMAIL = "update_email";
String UPDATE_PROFILE = "update_profile";
String UPDATE_PASSWORD = "update_password";
String UPDATE_TOTP = "update_totp";
String VERIFY_EMAIL = "verify_email";
String REMOVE_TOTP = "remove_totp";
String SEND_VERIFY_EMAIL = "send_verify_email";
String SEND_RESET_PASSWORD = "send_reset_password";
}

52
audit/email/pom.xml Executable file
View file

@ -0,0 +1,52 @@
<?xml version="1.0"?>
<project>
<parent>
<artifactId>keycloak-audit-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-beta-1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-audit-email</artifactId>
<name>Keycloak Audit Email Provider</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-email-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,57 @@
package org.keycloak.audit.email;
import org.jboss.logging.Logger;
import org.keycloak.audit.AuditListener;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class EmailAuditListener implements AuditListener {
private static final Logger log = Logger.getLogger(EmailAuditListener.class);
private KeycloakSession keycloakSession;
private EmailProvider emailProvider;
private Set<EventType> includedEvents;
public EmailAuditListener(KeycloakSession keycloakSession, EmailProvider emailProvider, Set<EventType> includedEvents) {
this.keycloakSession = keycloakSession;
this.emailProvider = emailProvider;
this.includedEvents = includedEvents;
}
@Override
public void onEvent(Event event) {
if (includedEvents.contains(event.getEvent())) {
if (event.getRealmId() != null && event.getUserId() != null) {
RealmModel realm = keycloakSession.getRealm(event.getRealmId());
UserModel user = realm.getUserById(event.getUserId());
if (user != null && user.getEmail() != null && user.isEmailVerified()) {
try {
emailProvider.setRealm(realm).setUser(user).sendEvent(event);
} catch (EmailException e) {
log.error("Failed to send event mail", e);
}
}
}
}
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,62 @@
package org.keycloak.audit.email;
import org.keycloak.Config;
import org.keycloak.audit.AuditListener;
import org.keycloak.audit.AuditListenerFactory;
import org.keycloak.audit.EventType;
import org.keycloak.email.EmailProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderSession;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class EmailAuditListenerFactory implements AuditListenerFactory {
private static final Set<EventType> SUPPORTED_EVENTS = new HashSet<EventType>();
static {
Collections.addAll(SUPPORTED_EVENTS, EventType.LOGIN_ERROR, EventType.UPDATE_PASSWORD, EventType.REMOVE_TOTP, EventType.UPDATE_TOTP);
}
private Set<EventType> includedEvents = new HashSet<EventType>();
@Override
public AuditListener create(ProviderSession providerSession) {
KeycloakSession keycloakSession = providerSession.getProvider(KeycloakSession.class);
EmailProvider emailProvider = providerSession.getProvider(EmailProvider.class);
return new EmailAuditListener(keycloakSession, emailProvider, includedEvents);
}
@Override
public void init(Config.Scope config) {
String[] include = config.getArray("include-events");
if (include != null) {
for (String i : include) {
includedEvents.add(EventType.valueOf(i.toUpperCase()));
}
} else {
includedEvents.addAll(SUPPORTED_EVENTS);
}
String[] exclude = config.getArray("exclude-events");
if (exclude != null) {
for (String e : exclude) {
includedEvents.remove(EventType.valueOf(e.toUpperCase()));
}
}
}
@Override
public void close() {
}
@Override
public String getId() {
return "email";
}
}

View file

@ -0,0 +1 @@
org.keycloak.audit.email.EmailAuditListenerFactory

View file

@ -6,11 +6,13 @@ import org.jboss.logging.Logger;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import org.keycloak.audit.EventType;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
@ -25,9 +27,11 @@ public class JpaAuditProvider implements AuditProvider {
private EntityManager em;
private EntityTransaction tx;
private Set<EventType> includedEvents;
public JpaAuditProvider(EntityManager em) {
public JpaAuditProvider(EntityManager em, Set<EventType> includedEvents) {
this.em = em;
this.includedEvents = includedEvents;
}
@Override
@ -55,8 +59,10 @@ public class JpaAuditProvider implements AuditProvider {
@Override
public void onEvent(Event event) {
beginTx();
em.persist(convert(event));
if (includedEvents.contains(event.getEvent())) {
beginTx();
em.persist(convert(event));
}
}
@Override
@ -79,7 +85,7 @@ public class JpaAuditProvider implements AuditProvider {
EventEntity e = new EventEntity();
e.setId(UUID.randomUUID().toString());
e.setTime(o.getTime());
e.setEvent(o.getEvent());
e.setEvent(o.getEvent().toString());
e.setRealmId(o.getRealmId());
e.setClientId(o.getClientId());
e.setUserId(o.getUserId());
@ -97,7 +103,7 @@ public class JpaAuditProvider implements AuditProvider {
static Event convert(EventEntity o) {
Event e = new Event();
e.setTime(o.getTime());
e.setEvent(o.getEvent());
e.setEvent(EventType.valueOf(o.getEvent()));
e.setRealmId(o.getRealmId());
e.setClientId(o.getClientId());
e.setUserId(o.getUserId());

View file

@ -3,10 +3,13 @@ package org.keycloak.audit.jpa;
import org.keycloak.Config;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.AuditProviderFactory;
import org.keycloak.audit.EventType;
import org.keycloak.provider.ProviderSession;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -16,14 +19,34 @@ public class JpaAuditProviderFactory implements AuditProviderFactory {
public static final String ID = "jpa";
private EntityManagerFactory emf;
private Set<EventType> includedEvents = new HashSet<EventType>();
@Override
public AuditProvider create(ProviderSession providerSession) {
return new JpaAuditProvider(emf.createEntityManager());
return new JpaAuditProvider(emf.createEntityManager(), includedEvents);
}
@Override
public void init(Config.Scope config) {
emf = Persistence.createEntityManagerFactory("jpa-keycloak-audit-store");
String[] include = config.getArray("include-events");
if (include != null) {
for (String i : include) {
includedEvents.add(EventType.valueOf(i.toUpperCase()));
}
} else {
for (EventType i : EventType.values()) {
includedEvents.add(i);
}
}
String[] exclude = config.getArray("exclude-events");
if (exclude != null) {
for (String e : exclude) {
includedEvents.remove(EventType.valueOf(e.toUpperCase()));
}
}
}
@Override

View file

@ -2,6 +2,7 @@ package org.keycloak.audit.jpa;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import org.keycloak.audit.EventType;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@ -36,8 +37,12 @@ public class JpaEventQuery implements EventQuery {
}
@Override
public EventQuery event(String... events) {
predicates.add(root.get("event").in(events));
public EventQuery event(EventType... events) {
List<String> eventStrings = new LinkedList<String>();
for (EventType e : events) {
eventStrings.add(e.toString());
}
predicates.add(root.get("event").in(eventStrings));
return this;
}

View file

@ -6,9 +6,11 @@ import com.mongodb.DBObject;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import org.keycloak.audit.EventType;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -16,9 +18,11 @@ import java.util.Map;
public class MongoAuditProvider implements AuditProvider {
private DBCollection audit;
private Set<EventType> includedEvents;
public MongoAuditProvider(DBCollection audit) {
public MongoAuditProvider(DBCollection audit, Set<EventType> includedEvents) {
this.audit = audit;
this.includedEvents = includedEvents;
}
@Override
@ -46,7 +50,9 @@ public class MongoAuditProvider implements AuditProvider {
@Override
public void onEvent(Event event) {
audit.insert(convert(event));
if (includedEvents.contains(event.getEvent())) {
audit.insert(convert(event));
}
}
@Override
@ -56,7 +62,7 @@ public class MongoAuditProvider implements AuditProvider {
static DBObject convert(Event o) {
BasicDBObject e = new BasicDBObject();
e.put("time", o.getTime());
e.put("event", o.getEvent());
e.put("event", o.getEvent().toString());
e.put("realmId", o.getRealmId());
e.put("clientId", o.getClientId());
e.put("userId", o.getUserId());
@ -78,7 +84,7 @@ public class MongoAuditProvider implements AuditProvider {
static Event convert(BasicDBObject o) {
Event e = new Event();
e.setTime(o.getLong("time"));
e.setEvent(o.getString("event"));
e.setEvent(EventType.valueOf(o.getString("event")));
e.setRealmId(o.getString("realmId"));
e.setClientId(o.getString("clientId"));
e.setUserId(o.getString("userId"));

View file

@ -8,10 +8,13 @@ import com.mongodb.WriteConcern;
import org.keycloak.Config;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.AuditProviderFactory;
import org.keycloak.audit.EventType;
import org.keycloak.provider.ProviderSession;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -22,9 +25,11 @@ public class MongoAuditProviderFactory implements AuditProviderFactory {
private MongoClient client;
private DB db;
private Set<EventType> includedEvents = new HashSet<EventType>();
@Override
public AuditProvider create(ProviderSession providerSession) {
return new MongoAuditProvider(db.getCollection("audit"));
return new MongoAuditProvider(db.getCollection("audit"), includedEvents);
}
@Override
@ -53,6 +58,24 @@ public class MongoAuditProviderFactory implements AuditProviderFactory {
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
String[] include = config.getArray("include-events");
if (include != null) {
for (String i : include) {
includedEvents.add(EventType.valueOf(i.toUpperCase()));
}
} else {
for (EventType i : EventType.values()) {
includedEvents.add(i);
}
}
String[] exclude = config.getArray("exclude-events");
if (exclude != null) {
for (String e : exclude) {
includedEvents.remove(EventType.valueOf(e.toUpperCase()));
}
}
}
@Override

View file

@ -5,9 +5,11 @@ import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import org.keycloak.audit.EventType;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -25,8 +27,12 @@ public class MongoEventQuery implements EventQuery {
}
@Override
public EventQuery event(String... events) {
query.put("event", new BasicDBObject("$in", events));
public EventQuery event(EventType... events) {
List<String> eventStrings = new LinkedList<String>();
for (EventType e : events) {
eventStrings.add(e.toString());
}
query.put("event", new BasicDBObject("$in", eventStrings));
return this;
}

View file

@ -17,6 +17,7 @@
<modules>
<module>api</module>
<module>email</module>
<module>jpa</module>
<module>jboss-logging</module>
<module>mongo</module>

View file

@ -8,6 +8,7 @@ import org.keycloak.Config;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.AuditProviderFactory;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.provider.ProviderFactory;
import java.util.HashMap;
@ -47,7 +48,7 @@ public abstract class AbstractAuditProviderTest {
@Test
public void save() {
provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
}
@Test
@ -55,23 +56,23 @@ public abstract class AbstractAuditProviderTest {
long oldest = System.currentTimeMillis() - 30000;
long newest = System.currentTimeMillis() + 30000;
provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(newest, "event2", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(newest, "event2", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
provider.onEvent(create("event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(oldest, "event", "realmId", "clientId2", "userId", "127.0.0.1", "error"));
provider.onEvent(create("event", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
provider.onEvent(create(EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(newest, EventType.REGISTER, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(newest, EventType.REGISTER, "realmId", "clientId", "userId2", "127.0.0.1", "error"));
provider.onEvent(create(EventType.LOGIN, "realmId2", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(oldest, EventType.LOGIN, "realmId", "clientId2", "userId", "127.0.0.1", "error"));
provider.onEvent(create(EventType.LOGIN, "realmId", "clientId", "userId2", "127.0.0.1", "error"));
provider.close();
provider = factory.create(null);
Assert.assertEquals(5, provider.createQuery().client("clientId").getResultList().size());
Assert.assertEquals(5, provider.createQuery().realm("realmId").getResultList().size());
Assert.assertEquals(4, provider.createQuery().event("event").getResultList().size());
Assert.assertEquals(6, provider.createQuery().event("event", "event2").getResultList().size());
Assert.assertEquals(4, provider.createQuery().event(EventType.LOGIN).getResultList().size());
Assert.assertEquals(6, provider.createQuery().event(EventType.LOGIN, EventType.REGISTER).getResultList().size());
Assert.assertEquals(4, provider.createQuery().user("userId").getResultList().size());
Assert.assertEquals(1, provider.createQuery().user("userId").event("event2").getResultList().size());
Assert.assertEquals(1, provider.createQuery().user("userId").event(EventType.REGISTER).getResultList().size());
Assert.assertEquals(2, provider.createQuery().maxResults(2).getResultList().size());
Assert.assertEquals(1, provider.createQuery().firstResult(5).getResultList().size());
@ -82,11 +83,11 @@ public abstract class AbstractAuditProviderTest {
@Test
public void clear() {
provider.onEvent(create(System.currentTimeMillis() - 30000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 20000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 30000, "event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 30000, EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 20000, EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis(), EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis(), EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 30000, EventType.LOGIN, "realmId2", "clientId", "userId", "127.0.0.1", "error"));
provider.close();
provider = factory.create(null);
@ -98,11 +99,11 @@ public abstract class AbstractAuditProviderTest {
@Test
public void clearOld() {
provider.onEvent(create(System.currentTimeMillis() - 30000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 20000, "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis(), "event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 30000, "event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 30000, EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 20000, EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis(), EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis(), EventType.LOGIN, "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create(System.currentTimeMillis() - 30000, EventType.LOGIN, "realmId2", "clientId", "userId", "127.0.0.1", "error"));
provider.close();
provider = factory.create(null);
@ -112,11 +113,11 @@ public abstract class AbstractAuditProviderTest {
Assert.assertEquals(3, provider.createQuery().getResultList().size());
}
private Event create(String event, String realmId, String clientId, String userId, String ipAddress, String error) {
private Event create(EventType event, String realmId, String clientId, String userId, String ipAddress, String error) {
return create(System.currentTimeMillis(), event, realmId, clientId, userId, ipAddress, error);
}
private Event create(long time, String event, String realmId, String clientId, String userId, String ipAddress, String error) {
private Event create(long time, EventType event, String realmId, String clientId, String userId, String ipAddress, String error) {
Event e = new Event();
e.setTime(time);
e.setEvent(event);

View file

@ -69,6 +69,20 @@ public class Config {
return System.getProperty(prefix + key, defaultValue);
}
@Override
public String[] getArray(String key) {
String value = get(key);
if (value != null) {
String[] a = value.split(",");
for (int i = 0; i < a.length; i++) {
a[i] = a[i].trim();
}
return a;
} else {
return null;
}
}
@Override
public Integer getInt(String key) {
return getInt(key, null);
@ -113,6 +127,8 @@ public class Config {
String get(String key, String defaultValue);
String[] getArray(String key);
Integer getInt(String key);
Integer getInt(String key, Integer defaultValue);

View file

@ -38,7 +38,7 @@ public class LogBean {
}
public String getEvent() {
return event.getEvent();
return event.getEvent().toString().toLowerCase().replace("_", " ");
}
public String getClient() {
@ -51,8 +51,10 @@ public class LogBean {
public List<DetailBean> getDetails() {
List<DetailBean> details = new LinkedList<DetailBean>();
for (Map.Entry<String, String> e : event.getDetails().entrySet()) {
details.add(new DetailBean(e));
if (event.getDetails() != null) {
for (Map.Entry<String, String> e : event.getDetails().entrySet()) {
details.add(new DetailBean(e));
}
}
return details;
}
@ -72,7 +74,7 @@ public class LogBean {
}
public String getValue() {
return entry.getValue();
return entry.getValue().replace("_", " ");
}
}

View file

@ -73,12 +73,13 @@
<tbody>
<tr ng-repeat="event in events">
<td>{{event.time|date:'shortDate'}}<br>{{event.time|date:'mediumTime'}}</td>
<td data-ng-class="event.error && 'audit-error' || 'audit-success'">{{event.event}}<br/>{{event.error}}</td>
<td data-ng-class="event.error && 'audit-error' || 'audit-success'">{{event.event}}</td>
<td>
<table>
<table class="table table-striped table-bordered">
<tr><td width="100px">Client</td><td>{{event.clientId}}</td></tr>
<tr><td>User</td><td>{{event.userId}}</td></tr>
<tr><td>IP Address</td><td>{{event.ipAddress}}</td></tr>
<tr data-ng-show="event.error"><td>Error</td><td>{{event.error}}</td></tr>
<tr>
<td>Details</td>
<td>
@ -86,7 +87,7 @@
<span class="glyphicon glyphicon-plus" data-ng-show="!event.collapse"></span>
<span class="glyphicon glyphicon-minus" data-ng-show="event.collapse"></span>
</button>
<table data-ng-show="event.collapse">
<table data-ng-show="event.collapse" class="table table-striped table-bordered">
<tr ng-repeat="(key, value) in event.details">
<td>{{key}}</td>
<td>{{value}}</td>

View file

@ -828,4 +828,8 @@ legend .kc-icon-collapse {
.action-div > i {
cursor: pointer;
}
table table {
margin-bottom: 0 !important;
}

View file

@ -1,3 +1,78 @@
table tfoot tr .table-nav {
float: right;
}
table tfoot tr .table-nav a,
table tfoot tr .table-nav button {
display: inline-block;
line-height: 22px;
border-left: 1px solid #d9d9d9;
border-right: none;
border-top: none;
border-bottom: none;
width: 3.5em;
background-color: #f3f3f3;
background-image: linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -o-linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -moz-linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -webkit-linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -ms-linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(1, 0, #ededed));
text-indent: -99999em;
background-image: url(img/sprite-table-nav.png);
background-repeat: no-repeat;
background-position: left top;
vertical-align: top;
}
table tfoot tr .table-nav a.last,
table tfoot tr .table-nav button.last {
background-position: top right;
}
table tfoot tr .table-nav a.prev,
table tfoot tr .table-nav button.prev {
background-position: bottom left;
}
table tfoot tr .table-nav a.next,
table tfoot tr .table-nav button.next {
background-position: bottom right;
}
table tfoot tr .table-nav a:hover,
table tfoot tr .table-nav button:hover {
background-image: url(img/sprite-table-nav.png);
background-color: #eeeeee;
}
table tfoot tr .table-nav a:active,
table tfoot tr .table-nav button:active {
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25) inset;
}
table tfoot tr .table-nav a.disabled,
table tfoot tr .table-nav button:disabled {
opacity: 0.5;
filter: alpha(opacity=50);
cursor: default;
}
table tfoot tr .table-nav a.disabled:active,
table tfoot tr .table-nav button:disabled:active {
box-shadow: none;
}
table tfoot tr .table-nav span {
font-size: 1.1em;
border-left: 1px solid #d9d9d9;
line-height: 2.18181818181818em;
display: inline-block;
padding: 0 1.36363636363636em;
}
td.audit-success {
background-color: #E4F1E1 !important;
}
td.audit-error {
background-color: #F8E7E7 !important;
}
/*
table {
width: 100%;
@ -111,69 +186,6 @@ table tbody.selectable-rows tr.selected:hover td:first-child {
table tfoot tr {
border-top: 1px solid #cecece;
}
table tfoot tr .table-nav {
float: right;
}
table tfoot tr .table-nav a,
table tfoot tr .table-nav button {
display: inline-block;
line-height: 22px;
border-left: 1px solid #d9d9d9;
border-right: none;
border-top: none;
border-bottom: none;
width: 3.5em;
background-color: #f3f3f3;
background-image: linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -o-linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -moz-linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -webkit-linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -ms-linear-gradient(top, #fafafa 0%, #ededed 100%);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fafafa), color-stop(1, 0, #ededed));
text-indent: -99999em;
background-image: url(img/sprite-table-nav.png);
background-repeat: no-repeat;
background-position: left top;
vertical-align: top;
}
table tfoot tr .table-nav a.last,
table tfoot tr .table-nav button.last {
background-position: top right;
}
table tfoot tr .table-nav a.prev,
table tfoot tr .table-nav button.prev {
background-position: bottom left;
}
table tfoot tr .table-nav a.next,
table tfoot tr .table-nav button.next {
background-position: bottom right;
}
table tfoot tr .table-nav a:hover,
table tfoot tr .table-nav button:hover {
background-image: url(img/sprite-table-nav.png);
background-color: #eeeeee;
}
table tfoot tr .table-nav a:active,
table tfoot tr .table-nav button:active {
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.25) inset;
}
table tfoot tr .table-nav a.disabled,
table tfoot tr .table-nav button:disabled {
opacity: 0.5;
filter: alpha(opacity=50);
cursor: default;
}
table tfoot tr .table-nav a.disabled:active,
table tfoot tr .table-nav button:disabled:active {
box-shadow: none;
}
table tfoot tr .table-nav span {
font-size: 1.1em;
border-left: 1px solid #d9d9d9;
line-height: 2.18181818181818em;
display: inline-block;
padding: 0 1.36363636363636em;
}
.kc-table-actions > * {
vertical-align: middle;
@ -183,14 +195,6 @@ td .form-group {
margin-bottom: 0;
}
td.audit-success {
background-color: #E4F1E1;
}
td.audit-error {
background-color: #F8E7E7;
}
.kc-table-actions .form-group {
margin-top: 5px;
margin-bottom: 5px;

View file

@ -0,0 +1 @@
Your password was changed on ${event.date} from ${event.ipAddress}. If this was not you, please contact an admin.

View file

@ -0,0 +1 @@
TOTP was removed from your account on ${event.date} from ${event.ipAddress}. If this was not you, please contact an admin.

View file

@ -0,0 +1 @@
A failed login attempt was dettected to your account on ${event.date} from ${event.ipAddress}. If this was not you, please contact an admin.

View file

@ -0,0 +1 @@
TOTP was updated for your account on ${event.date} from ${event.ipAddress}. If this was not you, please contact an admin.

View file

@ -26,6 +26,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>

View file

@ -1,5 +1,6 @@
package org.keycloak.email;
import org.keycloak.audit.Event;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
@ -13,6 +14,8 @@ public interface EmailProvider extends Provider {
public EmailProvider setUser(UserModel user);
public void sendEvent(Event event) throws EmailException;
public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException;
public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException;

View file

@ -32,6 +32,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-forms-common-freemarker</artifactId>

View file

@ -1,8 +1,10 @@
package org.keycloak.email.freemarker;
import org.jboss.logging.Logger;
import org.keycloak.audit.Event;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.email.freemarker.beans.EventBean;
import org.keycloak.freemarker.ExtendingThemeManager;
import org.keycloak.freemarker.FreeMarkerUtil;
import org.keycloak.freemarker.Theme;
@ -47,6 +49,14 @@ public class FreeMarkerEmailProvider implements EmailProvider {
return this;
}
@Override
public void sendEvent(Event event) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put("event", new EventBean(event));
send("passwordResetSubject", "event-" + event.getEvent().toString().toLowerCase() + ".ftl", attributes);
}
@Override
public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException {
Map<String, Object> attributes = new HashMap<String, Object>();

View file

@ -0,0 +1,61 @@
package org.keycloak.email.freemarker.beans;
import org.keycloak.audit.Event;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class EventBean {
private Event event;
public EventBean(Event event) {
this.event = event;
}
public Date getDate() {
return new Date(event.getTime());
}
public String getEvent() {
return event.getEvent().toString().toLowerCase().replace("_", " ");
}
public String getClient() {
return event.getClientId();
}
public String getIpAddress() {
return event.getIpAddress();
}
public List<DetailBean> getDetails() {
List<DetailBean> details = new LinkedList<DetailBean>();
for (Map.Entry<String, String> e : event.getDetails().entrySet()) {
details.add(new DetailBean(e));
}
return details;
}
public static class DetailBean {
private Map.Entry<String, String> entry;
public DetailBean(Map.Entry<String, String> entry) {
this.entry = entry;
}
public String getKey() {
return entry.getKey();
}
public String getValue() {
return entry.getValue().replace("_", " ");
}
}
}

View file

@ -3,10 +3,6 @@
"realm": "keycloak-admin"
},
"audit": {
"provider": "jpa"
},
"model": {
"provider": "jpa"
},

View file

@ -62,6 +62,11 @@
<artifactId>keycloak-audit-jboss-logging</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-email</artifactId>
<version>${project.version}</version>
</dependency>
<!-- social -->
<dependency>
<groupId>org.keycloak</groupId>

View file

@ -4,7 +4,10 @@
},
"audit": {
"provider": "jpa"
"provider": "jpa",
"jpa": {
"exclude-events": [ "REFRESH_TOKEN" ]
}
},
"model": {

View file

@ -31,7 +31,7 @@ import org.keycloak.audit.Audit;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.Details;
import org.keycloak.audit.Event;
import org.keycloak.audit.Events;
import org.keycloak.audit.EventType;
import org.keycloak.authentication.AuthProviderStatus;
import org.keycloak.authentication.AuthenticationProviderException;
import org.keycloak.authentication.AuthenticationProviderManager;
@ -95,8 +95,8 @@ public class AccountService {
private static final Logger logger = Logger.getLogger(AccountService.class);
private static final String[] AUDIT_EVENTS = {Events.LOGIN, Events.LOGOUT, Events.REGISTER, Events.REMOVE_SOCIAL_LINK, Events.REMOVE_TOTP, Events.SEND_RESET_PASSWORD,
Events.SEND_VERIFY_EMAIL, Events.SOCIAL_LINK, Events.UPDATE_EMAIL, Events.UPDATE_PASSWORD, Events.UPDATE_PROFILE, Events.UPDATE_TOTP, Events.VERIFY_EMAIL};
private static final EventType[] AUDIT_EVENTS = {EventType.LOGIN, EventType.LOGOUT, EventType.REGISTER, EventType.REMOVE_SOCIAL_LINK, EventType.REMOVE_TOTP, EventType.SEND_RESET_PASSWORD,
EventType.SEND_VERIFY_EMAIL, EventType.SOCIAL_LINK, EventType.UPDATE_EMAIL, EventType.UPDATE_PASSWORD, EventType.UPDATE_PROFILE, EventType.UPDATE_TOTP, EventType.VERIFY_EMAIL};
private static final Set<String> AUDIT_DETAILS = new HashSet<String>();
static {
@ -246,20 +246,6 @@ public class AccountService {
public Response logPage() {
if (auth != null) {
List<Event> events = auditProvider.createQuery().event(AUDIT_EVENTS).user(auth.getUser().getId()).maxResults(30).getResultList();
for (Event e : events) {
e.setEvent(e.getEvent().replace('_', ' '));
Map<String, String> details = new HashMap<String, String>();
if (e.getDetails() != null) {
Iterator<String> itr = e.getDetails().keySet().iterator();
for (Map.Entry<String, String> d : e.getDetails().entrySet()) {
if (AUDIT_DETAILS.contains(d.getKey())) {
details.put(d.getKey().replace('_', ' '), d.getValue());
}
}
}
e.setDetails(details);
}
account.setEvents(events);
}
return forwardToPage("log", AccountPages.LOG);
@ -300,11 +286,11 @@ public class AccountService {
user.setEmail(formData.getFirst("email"));
audit.event(Events.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
audit.event(EventType.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
if (emailChanged) {
user.setEmailVerified(false);
audit.clone().event(Events.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
audit.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
}
return account.setSuccess("accountUpdated").createResponse(AccountPages.ACCOUNT);
@ -322,7 +308,7 @@ public class AccountService {
UserModel user = auth.getUser();
user.setTotp(false);
audit.event(Events.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
audit.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
return account.setSuccess("successTotpRemoved").createResponse(AccountPages.TOTP);
}
@ -371,7 +357,7 @@ public class AccountService {
user.setTotp(true);
audit.event(Events.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
audit.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
return account.setSuccess("successTotp").createResponse(AccountPages.TOTP);
}
@ -414,7 +400,7 @@ public class AccountService {
return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
}
audit.event(Events.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
audit.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
}
@ -471,7 +457,7 @@ public class AccountService {
logger.debug("Social provider " + providerId + " removed successfully from user " + user.getLoginName());
audit.event(Events.REMOVE_SOCIAL_LINK).client(auth.getClient()).user(auth.getUser())
audit.event(EventType.REMOVE_SOCIAL_LINK).client(auth.getClient()).user(auth.getUser())
.detail(Details.USERNAME, link.getSocialUserId() + "@" + link.getSocialProvider())
.success();

View file

@ -27,7 +27,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.audit.Audit;
import org.keycloak.audit.Details;
import org.keycloak.audit.Errors;
import org.keycloak.audit.Events;
import org.keycloak.audit.EventType;
import org.keycloak.email.EmailException;
import org.keycloak.email.EmailProvider;
import org.keycloak.login.LoginFormsProvider;
@ -136,10 +136,10 @@ public class RequiredActionsService {
user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PROFILE);
audit.clone().event(Events.UPDATE_PROFILE).success();
audit.clone().event(EventType.UPDATE_PROFILE).success();
if (emailChanged) {
user.setEmailVerified(false);
audit.clone().event(Events.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
audit.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
}
return redirectOauth(user, accessCode);
@ -178,7 +178,7 @@ public class RequiredActionsService {
user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
accessCode.getRequiredActions().remove(RequiredAction.CONFIGURE_TOTP);
audit.clone().event(Events.UPDATE_TOTP).success();
audit.clone().event(EventType.UPDATE_TOTP).success();
return redirectOauth(user, accessCode);
}
@ -225,7 +225,7 @@ public class RequiredActionsService {
accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
}
audit.clone().event(Events.UPDATE_PASSWORD).success();
audit.clone().event(EventType.UPDATE_PASSWORD).success();
return redirectOauth(user, accessCode);
}
@ -250,7 +250,7 @@ public class RequiredActionsService {
user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
accessCode.getRequiredActions().remove(RequiredAction.VERIFY_EMAIL);
audit.clone().event(Events.VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
audit.clone().event(EventType.VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
return redirectOauth(user, accessCode);
} else {
@ -260,7 +260,7 @@ public class RequiredActionsService {
}
initAudit(accessCode);
//audit.clone().event(Events.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
//audit.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
return Flows.forms(providerSession, realm, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(accessCode.getUser())
.createResponse(RequiredAction.VERIFY_EMAIL);
@ -307,7 +307,7 @@ public class RequiredActionsService {
"Login requester not enabled.");
}
audit.event(Events.SEND_RESET_PASSWORD).client(clientId)
audit.event(EventType.SEND_RESET_PASSWORD).client(clientId)
.detail(Details.REDIRECT_URI, redirect)
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.AUTH_METHOD, "form")
@ -430,7 +430,7 @@ public class RequiredActionsService {
}
private void initAudit(AccessCodeEntry accessCode) {
audit.event(Events.LOGIN).client(accessCode.getClient())
audit.event(EventType.LOGIN).client(accessCode.getClient())
.user(accessCode.getUser())
.session(accessCode.getSessionState())
.detail(Details.CODE_ID, accessCode.getId())

View file

@ -28,7 +28,7 @@ import org.keycloak.OAuth2Constants;
import org.keycloak.audit.Audit;
import org.keycloak.audit.Details;
import org.keycloak.audit.Errors;
import org.keycloak.audit.Events;
import org.keycloak.audit.EventType;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@ -65,7 +65,6 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URISyntaxException;
@ -131,7 +130,7 @@ public class SocialResource {
RealmModel realm = realmManager.getRealmByName(realmName);
Audit audit = new AuditManager(realm, providers, clientConnection).createAudit()
.event(Events.LOGIN)
.event(EventType.LOGIN)
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.AUTH_METHOD, "social@" + provider.getId());
@ -196,7 +195,7 @@ public class SocialResource {
if (userId != null) {
UserModel authenticatedUser = realm.getUserById(userId);
audit.event(Events.SOCIAL_LINK).user(userId);
audit.event(EventType.SOCIAL_LINK).user(userId);
if (user != null) {
audit.error(Errors.SOCIAL_ID_IN_USE);
@ -244,7 +243,7 @@ public class SocialResource {
realm.addSocialLink(user, socialLink);
audit.clone().user(user).event(Events.REGISTER)
audit.clone().user(user).event(EventType.REGISTER)
.detail(Details.REGISTER_METHOD, "social@" + provider.getId())
.detail(Details.EMAIL, socialUser.getEmail())
.removeDetail("auth_method")
@ -274,7 +273,7 @@ public class SocialResource {
RealmModel realm = realmManager.getRealmByName(realmName);
Audit audit = new AuditManager(realm, providers, clientConnection).createAudit()
.event(Events.LOGIN).client(clientId)
.event(EventType.LOGIN).client(clientId)
.detail(Details.REDIRECT_URI, redirectUri)
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.AUTH_METHOD, "social@" + providerId);

View file

@ -13,7 +13,7 @@ import org.keycloak.OAuthErrorException;
import org.keycloak.audit.Audit;
import org.keycloak.audit.Details;
import org.keycloak.audit.Errors;
import org.keycloak.audit.Events;
import org.keycloak.audit.EventType;
import org.keycloak.authentication.AuthenticationProviderException;
import org.keycloak.authentication.AuthenticationProviderManager;
import org.keycloak.jose.jws.JWSInput;
@ -34,7 +34,6 @@ import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.ClientConnection;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
@ -212,7 +211,7 @@ public class TokenService {
return createError("not_enabled", "Resource Owner Password Credentials Grant not enabled", Response.Status.FORBIDDEN);
}
audit.event(Events.LOGIN).detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
audit.event(EventType.LOGIN).detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
if (username == null) {
@ -294,7 +293,7 @@ public class TokenService {
throw new NotAcceptableException("HTTPS required");
}
audit.event(Events.REFRESH_TOKEN);
audit.event(EventType.REFRESH_TOKEN);
ClientModel client = authorizeClient(authorizationHeader, form, audit);
String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
@ -334,7 +333,7 @@ public class TokenService {
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
logger.debug("*** Remember me: " + remember);
audit.event(Events.LOGIN).client(clientId)
audit.event(EventType.LOGIN).client(clientId)
.detail(Details.REDIRECT_URI, redirect)
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.AUTH_METHOD, "form")
@ -383,12 +382,14 @@ public class TokenService {
authManager.expireRememberMeCookie(realm, uriInfo);
}
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
if (user != null) {
audit.user(user);
}
switch (status) {
case SUCCESS:
case ACTIONS_REQUIRED:
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
audit.user(user);
UserSessionModel session = realm.createUserSession(user, clientConnection.getRemoteAddr());
audit.session(session);
@ -429,7 +430,7 @@ public class TokenService {
String username = formData.getFirst("username");
String email = formData.getFirst("email");
audit.event(Events.REGISTER).client(clientId)
audit.event(EventType.REGISTER).client(clientId)
.detail(Details.REDIRECT_URI, redirect)
.detail(Details.RESPONSE_TYPE, "code")
.detail(Details.USERNAME, username)
@ -545,7 +546,7 @@ public class TokenService {
throw new NotAcceptableException("HTTPS required");
}
audit.event(Events.CODE_TO_TOKEN);
audit.event(EventType.CODE_TO_TOKEN);
if (!realm.isEnabled()) {
audit.error(Errors.REALM_DISABLED);
@ -724,7 +725,7 @@ public class TokenService {
final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state, final @QueryParam("prompt") String prompt) {
logger.info("TokenService.loginPage");
audit.event(Events.LOGIN).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
audit.event(EventType.LOGIN).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
OAuthFlows oauth = Flows.oauth(providerSession, realm, request, uriInfo, authManager, tokenManager);
@ -784,7 +785,7 @@ public class TokenService {
final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
logger.info("**********registerPage()");
audit.event(Events.REGISTER).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
audit.event(EventType.REGISTER).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
OAuthFlows oauth = Flows.oauth(providerSession, realm, request, uriInfo, authManager, tokenManager);
@ -833,7 +834,7 @@ public class TokenService {
public Response logout(final @QueryParam("session_state") String sessionState, final @QueryParam("redirect_uri") String redirectUri) {
// todo do we care if anybody can trigger this?
audit.event(Events.LOGOUT);
audit.event(EventType.LOGOUT);
if (redirectUri != null) {
audit.detail(Details.REDIRECT_URI, redirectUri);
}
@ -874,7 +875,7 @@ public class TokenService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processOAuth(final MultivaluedMap<String, String> formData) {
audit.event(Events.LOGIN).detail(Details.RESPONSE_TYPE, "code");
audit.event(EventType.LOGIN).detail(Details.RESPONSE_TYPE, "code");
OAuthFlows oauth = Flows.oauth(providerSession, realm, request, uriInfo, authManager, tokenManager);

View file

@ -7,6 +7,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import org.keycloak.audit.EventType;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
@ -236,7 +237,7 @@ public class RealmAdminResource {
query.client(client);
}
if (event != null) {
query.event(event);
query.event(EventType.valueOf(event));
}
if (user != null) {
query.user(user);

View file

@ -26,7 +26,7 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuth2Constants;
import org.keycloak.audit.Audit;
import org.keycloak.audit.Details;
import org.keycloak.audit.Events;
import org.keycloak.audit.EventType;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@ -140,7 +140,7 @@ public class OAuthFlows {
RequiredAction action = user.getRequiredActions().iterator().next();
if (action.equals(RequiredAction.VERIFY_EMAIL)) {
audit.clone().event(Events.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
audit.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
}
return Flows.forms(providerSession, realm, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(user)

View file

@ -4,6 +4,8 @@ import org.codehaus.jackson.JsonNode;
import org.keycloak.Config;
import org.keycloak.util.StringPropertyReplacer;
import java.util.ArrayList;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@ -62,6 +64,26 @@ public class JsonConfigProvider implements Config.ConfigProvider {
return StringPropertyReplacer.replaceProperties(n.getTextValue());
}
@Override
public String[] getArray(String key) {
if (config == null) {
return null;
}
JsonNode n = config.get(key);
if (n == null) {
return null;
} else if (n.isArray()) {
ArrayList<String> l = new ArrayList<String>();
for (JsonNode e : n) {
l.add(StringPropertyReplacer.replaceProperties(e.getTextValue()));
}
return (String[]) l.toArray();
} else {
return new String[] { StringPropertyReplacer.replaceProperties(n.getTextValue()) };
}
}
@Override
public Integer getInt(String key) {
return getInt(key, null);

View file

@ -100,6 +100,11 @@
<artifactId>keycloak-audit-jboss-logging</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-email</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>

View file

@ -12,6 +12,7 @@ import org.keycloak.audit.AuditListener;
import org.keycloak.audit.AuditListenerFactory;
import org.keycloak.audit.Details;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -110,12 +111,12 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
events.clear();
}
public ExpectedEvent expectRequiredAction(String event) {
public ExpectedEvent expectRequiredAction(EventType event) {
return expectLogin().event(event).session(isUUID());
}
public ExpectedEvent expectLogin() {
return expect("login")
return expect(EventType.LOGIN)
.detail(Details.CODE_ID, isCodeId())
.detail(Details.USERNAME, DEFAULT_USERNAME)
.detail(Details.RESPONSE_TYPE, "code")
@ -125,7 +126,7 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
}
public ExpectedEvent expectCodeToToken(String codeId, String sessionId) {
return expect("code_to_token")
return expect(EventType.CODE_TO_TOKEN)
.detail(Details.CODE_ID, codeId)
.detail(Details.TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_ID, isUUID())
@ -133,7 +134,7 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
}
public ExpectedEvent expectRefresh(String refreshTokenId, String sessionId) {
return expect("refresh_token")
return expect(EventType.REFRESH_TOKEN)
.detail(Details.TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_ID, refreshTokenId)
.detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID())
@ -141,14 +142,14 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
}
public ExpectedEvent expectLogout(String sessionId) {
return expect("logout").client((String) null)
return expect(EventType.LOGOUT).client((String) null)
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI)
.session(sessionId);
}
public ExpectedEvent expectRegister(String username, String email) {
UserRepresentation user = keycloak.getUser("test", username);
return expect("register")
return expect(EventType.REGISTER)
.user(user != null ? user.getId() : null)
.detail(Details.USERNAME, username)
.detail(Details.EMAIL, email)
@ -157,11 +158,11 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
.detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
}
public ExpectedEvent expectAccount(String event) {
public ExpectedEvent expectAccount(EventType event) {
return expect(event).client("account");
}
public ExpectedEvent expect(String event) {
public ExpectedEvent expect(EventType event) {
return new ExpectedEvent()
.realm(DEFAULT_REALM)
.client(DEFAULT_CLIENT_ID)
@ -253,7 +254,7 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
return this;
}
public ExpectedEvent event(String e) {
public ExpectedEvent event(EventType e) {
expected.setEvent(e);
return this;
}
@ -291,6 +292,9 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
}
public Event assertEvent(Event actual) {
if (expected.getError() != null && !expected.getEvent().toString().endsWith("_ERROR")) {
expected.setEvent(EventType.valueOf(expected.getEvent().toString() + "_ERROR"));
}
Assert.assertEquals(expected.getEvent(), actual.getEvent());
Assert.assertEquals(expected.getRealmId(), actual.getRealmId());
Assert.assertEquals(expected.getClientId(), actual.getClientId());

View file

@ -29,6 +29,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.audit.Details;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
@ -215,7 +216,7 @@ public class AccountTest {
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
events.expectAccount("update_password").assertEvent();
events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent();
changePasswordPage.logout();
@ -226,7 +227,7 @@ public class AccountTest {
Assert.assertEquals("Invalid username or password.", loginPage.getError());
events.expectLogin().user((String) null).session((String) null).error("invalid_user_credentials").removeDetail(Details.CODE_ID).assertEvent();
events.expectLogin().session((String) null).error("invalid_user_credentials").removeDetail(Details.CODE_ID).assertEvent();
loginPage.open();
loginPage.login("test-user@localhost", "new-password");
@ -260,7 +261,7 @@ public class AccountTest {
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
events.expectAccount("update_password").assertEvent();
events.expectAccount(EventType.UPDATE_PASSWORD).assertEvent();
} finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override
@ -317,8 +318,8 @@ public class AccountTest {
Assert.assertEquals("New last", profilePage.getLastName());
Assert.assertEquals("new@email.com", profilePage.getEmail());
events.expectAccount("update_profile").assertEvent();
events.expectAccount("update_email").detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
events.expectAccount(EventType.UPDATE_PROFILE).assertEvent();
events.expectAccount(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
}
@Test
@ -341,13 +342,13 @@ public class AccountTest {
Assert.assertEquals("Google authenticator configured.", profilePage.getSuccess());
events.expectAccount("update_totp").assertEvent();
events.expectAccount(EventType.UPDATE_TOTP).assertEvent();
Assert.assertTrue(driver.getPageSource().contains("pficon-delete"));
totpPage.removeTotp();
events.expectAccount("remove_totp").assertEvent();
events.expectAccount(EventType.REMOVE_TOTP).assertEvent();
}
@Test
@ -405,7 +406,7 @@ public class AccountTest {
Iterator<List<String>> itr = logPage.getEvents().iterator();
for (Event event : e) {
List<String> a = itr.next();
Assert.assertEquals(event.getEvent().replace('_', ' '), a.get(1));
Assert.assertEquals(event.getEvent().toString().replace('_', ' ').toLowerCase(), a.get(1));
Assert.assertEquals(event.getIpAddress(), a.get(2));
Assert.assertEquals(event.getClientId(), a.get(3));
}

View file

@ -28,6 +28,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.audit.Details;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
@ -116,7 +117,7 @@ public class RequiredActionEmailVerificationTest {
String body = (String) message.getContent();
String verificationUrl = MailUtil.getLink(body);
Event sendEvent = events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent();
Event sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost").assertEvent();
String sessionId = sendEvent.getSessionId();
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
@ -125,7 +126,7 @@ public class RequiredActionEmailVerificationTest {
driver.navigate().to(verificationUrl.trim());
events.expectRequiredAction("verify_email").session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
events.expectRequiredAction(EventType.VERIFY_EMAIL).session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -148,7 +149,7 @@ public class RequiredActionEmailVerificationTest {
String body = (String) message.getContent();
Event sendEvent = events.expectRequiredAction("send_verify_email").user(userId).detail("username", "verifyEmail").detail("email", "email").assertEvent();
Event sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).user(userId).detail("username", "verifyEmail").detail("email", "email").assertEvent();
String sessionId = sendEvent.getSessionId();
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
@ -159,7 +160,7 @@ public class RequiredActionEmailVerificationTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectRequiredAction("verify_email").user(userId).session(sessionId).detail("username", "verifyEmail").detail("email", "email").detail(Details.CODE_ID, mailCodeId).assertEvent();
events.expectRequiredAction(EventType.VERIFY_EMAIL).user(userId).session(sessionId).detail("username", "verifyEmail").detail("email", "email").detail(Details.CODE_ID, mailCodeId).assertEvent();
events.expectLogin().user(userId).session(sessionId).detail("username", "verifyEmail").detail(Details.CODE_ID, mailCodeId).assertEvent();
}
@ -173,7 +174,7 @@ public class RequiredActionEmailVerificationTest {
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
Event sendEvent = events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent();
Event sendEvent = events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).detail("email", "test-user@localhost").assertEvent();
String sessionId = sendEvent.getSessionId();
String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
@ -186,7 +187,7 @@ public class RequiredActionEmailVerificationTest {
String body = (String) message.getContent();
events.expectRequiredAction("send_verify_email").session(sessionId).detail("email", "test-user@localhost").assertEvent(sendEvent);
events.expectRequiredAction(EventType.SEND_VERIFY_EMAIL).session(sessionId).detail("email", "test-user@localhost").assertEvent(sendEvent);
String verificationUrl = MailUtil.getLink(body);
@ -194,7 +195,7 @@ public class RequiredActionEmailVerificationTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectRequiredAction("verify_email").session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
events.expectRequiredAction(EventType.VERIFY_EMAIL).session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
events.expectLogin().session(sessionId).assertEvent();
}

View file

@ -26,6 +26,7 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.audit.Details;
import org.keycloak.audit.EventType;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
@ -112,7 +113,7 @@ public class RequiredActionMultipleActionsTest {
public String updatePassword(String sessionId) {
changePasswordPage.changePassword("new-password", "new-password");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction("update_password");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_PASSWORD);
if (sessionId != null) {
expectedEvent.session(sessionId);
}
@ -122,12 +123,12 @@ public class RequiredActionMultipleActionsTest {
public String updateProfile(String sessionId) {
updateProfilePage.update("New first", "New last", "new@email.com");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction("update_profile");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_PROFILE);
if (sessionId != null) {
expectedEvent.session(sessionId);
}
sessionId = expectedEvent.assertEvent().getSessionId();
events.expectRequiredAction("update_email").session(sessionId).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
events.expectRequiredAction(EventType.UPDATE_EMAIL).session(sessionId).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
return sessionId;
}

View file

@ -26,6 +26,7 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
@ -93,7 +94,7 @@ public class RequiredActionResetPasswordTest {
changePasswordPage.assertCurrent();
changePasswordPage.changePassword("new-password", "new-password");
String sessionId = events.expectRequiredAction("update_password").assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent().getSessionId();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());

View file

@ -27,6 +27,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.audit.Details;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.CredentialRepresentation;
@ -109,7 +110,7 @@ public class RequiredActionTotpSetupTest {
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
String sessionId = events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp").assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp").assertEvent().getSessionId();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -127,7 +128,7 @@ public class RequiredActionTotpSetupTest {
totpPage.configure(totp.generate(totpSecret));
String sessionId = events.expectRequiredAction("update_totp").assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -164,7 +165,7 @@ public class RequiredActionTotpSetupTest {
// After totp config, user should be on the app page
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
Event loginEvent = events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
@ -192,7 +193,7 @@ public class RequiredActionTotpSetupTest {
// Remove google authentificator
accountTotpPage.removeTotp();
events.expectAccount("remove_totp").user(userId).assertEvent();
events.expectAccount(EventType.REMOVE_TOTP).user(userId).assertEvent();
// Logout
oauth.openLogout();
@ -206,7 +207,7 @@ public class RequiredActionTotpSetupTest {
totpPage.assertCurrent();
totpPage.configure(totp.generate(totpPage.getTotpSecret()));
String sessionId = events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent().getSessionId();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());

View file

@ -27,6 +27,7 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.audit.Details;
import org.keycloak.audit.EventType;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
@ -87,8 +88,8 @@ public class RequiredActionUpdateProfileTest {
updateProfilePage.update("New first", "New last", "new@email.com");
String sessionId = events.expectRequiredAction("update_profile").assertEvent().getSessionId();
events.expectRequiredAction("update_email").session(sessionId).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
String sessionId = events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_EMAIL).session(sessionId).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());

View file

@ -94,7 +94,7 @@ public class LoginTest {
Assert.assertEquals("Invalid username or password.", loginPage.getError());
events.expectLogin().user((String) null).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
}
@Test

View file

@ -111,7 +111,7 @@ public class LoginTotpTest {
loginPage.assertCurrent();
Assert.assertEquals("Invalid username or password.", loginPage.getError());
events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).user((String) null).session((String) null).assertEvent();
events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).session((String) null).assertEvent();
}
@Test

View file

@ -27,6 +27,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.audit.Details;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
@ -125,7 +126,7 @@ public class ResetPasswordTest {
resetPasswordPage.assertCurrent();
String sessionId = events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
@ -142,7 +143,7 @@ public class ResetPasswordTest {
updatePasswordPage.changePassword("resetPassword", "resetPassword");
events.expectRequiredAction("update_password").user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -178,7 +179,7 @@ public class ResetPasswordTest {
Assert.assertEquals(0, greenMail.getReceivedMessages().length);
events.expectRequiredAction("send_reset_password").user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
}
@Test
@ -208,7 +209,7 @@ public class ResetPasswordTest {
String body = (String) message.getContent();
String changePasswordUrl = MailUtil.getLink(body);
String sessionId = events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent().getSessionId();
driver.navigate().to(changePasswordUrl.trim());
@ -220,7 +221,7 @@ public class ResetPasswordTest {
updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
events.expectRequiredAction("update_password").user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, "login-test").assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());

View file

@ -29,6 +29,7 @@ import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.audit.Details;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventType;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.UserRepresentation;
@ -110,7 +111,7 @@ public class SocialLoginTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
String userId = events.expect("register")
String userId = events.expect(EventType.REGISTER)
.user(AssertEvents.isUUID())
.detail(Details.EMAIL, "bob@builder.com")
.detail(Details.RESPONSE_TYPE, "code")
@ -202,7 +203,7 @@ public class SocialLoginTest {
Assert.assertEquals("Builder", profilePage.getLastName());
Assert.assertEquals("bob@builder.com", profilePage.getEmail());
String userId = events.expect("register")
String userId = events.expect(EventType.REGISTER)
.user(AssertEvents.isUUID())
.detail(Details.EMAIL, "bob@builder.com")
.detail(Details.RESPONSE_TYPE, "code")
@ -213,8 +214,8 @@ public class SocialLoginTest {
profilePage.update("Dummy", "User", "dummy-user-reg@dummy-social");
events.expectRequiredAction("update_profile").user(userId).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").assertEvent();
events.expectRequiredAction("update_email").user(userId).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").detail(Details.PREVIOUS_EMAIL, "bob@builder.com").detail(Details.UPDATED_EMAIL, "dummy-user-reg@dummy-social").assertEvent();
events.expectRequiredAction(EventType.UPDATE_PROFILE).user(userId).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").assertEvent();
events.expectRequiredAction(EventType.UPDATE_EMAIL).user(userId).detail(Details.AUTH_METHOD, "social@dummy").detail(Details.USERNAME, "2@dummy").detail(Details.PREVIOUS_EMAIL, "bob@builder.com").detail(Details.UPDATED_EMAIL, "dummy-user-reg@dummy-social").assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());