Merge pull request #311 from stianst/master

Added JPA and Mongo audit providers. Add audit deps to server war.
This commit is contained in:
Stian Thorgersen 2014-04-02 14:05:07 +01:00
commit 8af81cd290
34 changed files with 1168 additions and 46 deletions

View file

@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderFactoryLoader;
import java.util.HashMap;
import java.util.LinkedList;
@ -20,14 +21,17 @@ public class Audit {
private Event event;
public static Audit create(RealmModel realm, String ipAddress) {
ProviderFactoryLoader<AuditListenerFactory> loader = ProviderFactoryLoader.load(AuditListenerFactory.class);
List<AuditListener> listeners = null;
if (realm.getAuditListeners() != null) {
listeners = new LinkedList<AuditListener>();
for (String id : realm.getAuditListeners()) {
listeners.add(AuditLoader.load(id));
listeners.add(loader.find(id).create());
}
}
return new Audit(listeners, new Event()).realm(realm).ipAddress(ipAddress);
}

View file

@ -1,11 +1,11 @@
package org.keycloak.audit;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface AuditListener {
public String getId();
public interface AuditListener extends Provider {
public void onEvent(Event event);

View file

@ -0,0 +1,10 @@
package org.keycloak.audit;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface AuditListenerFactory extends ProviderFactory<AuditListener> {
}

View file

@ -1,31 +0,0 @@
package org.keycloak.audit;
import org.keycloak.util.ProviderLoader;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AuditLoader {
private AuditLoader() {
}
public static AuditListener load(String id) {
if (id == null) {
throw new NullPointerException();
}
for (AuditListener l : load()) {
if (id.equals(l.getId())) {
return l;
}
}
return null;
}
public static Iterable<AuditListener> load() {
return ProviderLoader.load(AuditListener.class);
}
}

View file

@ -7,4 +7,8 @@ public interface AuditProvider extends AuditListener {
public EventQuery createQuery();
public void clear();
public void clear(long olderThan);
}

View file

@ -0,0 +1,10 @@
package org.keycloak.audit;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface AuditProviderFactory extends ProviderFactory<AuditProvider> {
}

View file

@ -17,6 +17,12 @@
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-api</artifactId>

View file

@ -11,11 +11,10 @@ import java.util.Map;
*/
public class JBossLoggingAuditListener implements AuditListener {
private static final Logger logger = Logger.getLogger("org.keycloak.audit");
private final Logger logger;
@Override
public String getId() {
return "jboss-logging";
public JBossLoggingAuditListener(Logger logger) {
this.logger = logger;
}
@Override
@ -60,4 +59,8 @@ public class JBossLoggingAuditListener implements AuditListener {
}
}
@Override
public void close() {
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.audit.log;
import org.jboss.logging.Logger;
import org.keycloak.audit.AuditListener;
import org.keycloak.audit.AuditListenerFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JBossLoggingAuditListenerFactory implements AuditListenerFactory {
public static final String ID = "jboss-logging";
private static final Logger logger = Logger.getLogger("org.keycloak.audit");
@Override
public AuditListener create() {
return new JBossLoggingAuditListener(logger);
}
@Override
public void init() {
}
@Override
public void close() {
}
@Override
public String getId() {
return ID;
}
}

View file

@ -1 +0,0 @@
org.keycloak.audit.log.JBossLoggingAuditListener

View file

@ -0,0 +1 @@
org.keycloak.audit.log.JBossLoggingAuditListenerFactory

View file

@ -19,11 +19,43 @@
<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-audit-tests</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.entitymanager.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,103 @@
package org.keycloak.audit.jpa;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
@Entity
public class EventEntity {
@Id
private String id;
private long time;
private String event;
private String realmId;
private String clientId;
private String userId;
private String ipAddress;
private String error;
private String detailsJson;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getEvent() {
return event;
}
public void setEvent(String event) {
this.event = event;
}
public String getRealmId() {
return realmId;
}
public void setRealmId(String realmId) {
this.realmId = realmId;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getDetailsJson() {
return detailsJson;
}
public void setDetailsJson(String detailsJson) {
this.detailsJson = detailsJson;
}
}

View file

@ -0,0 +1,99 @@
package org.keycloak.audit.jpa;
import org.json.JSONObject;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JpaAuditProvider implements AuditProvider {
private EntityManager em;
private EntityTransaction tx;
public JpaAuditProvider(EntityManager em) {
this.em = em;
}
@Override
public EventQuery createQuery() {
return new JpaEventQuery(em);
}
@Override
public void clear() {
beginTx();
em.createQuery("delete from EventEntity").executeUpdate();
}
@Override
public void clear(long olderThan) {
beginTx();
em.createQuery("delete from EventEntity where time < :time").setParameter("time", olderThan).executeUpdate();
}
@Override
public void onEvent(Event event) {
beginTx();
em.persist(convert(event));
}
@Override
public void close() {
if (tx != null) {
tx.commit();
}
em.close();
}
private void beginTx() {
if (tx == null) {
tx = em.getTransaction();
tx.begin();
}
}
static EventEntity convert(Event o) {
EventEntity e = new EventEntity();
e.setId(UUID.randomUUID().toString());
e.setTime(o.getTime());
e.setEvent(o.getEvent());
e.setRealmId(o.getRealmId());
e.setClientId(o.getClientId());
e.setUserId(o.getUserId());
e.setIpAddress(o.getIpAddress());
e.setError(o.getError());
e.setDetailsJson(new JSONObject(o.getDetails()).toString());
return e;
}
static Event convert(EventEntity o) {
Event e = new Event();
e.setTime(o.getTime());
e.setEvent(o.getEvent());
e.setRealmId(o.getRealmId());
e.setClientId(o.getClientId());
e.setUserId(o.getUserId());
e.setIpAddress(o.getIpAddress());
e.setError(o.getError());
JSONObject object = new JSONObject(o.getDetailsJson());
Map<String, String> details = new HashMap<String, String>();
for (Object k : object.keySet()) {
details.put((String) k, object.getString((String) k));
}
e.setDetails(details);
return e;
}
}

View file

@ -0,0 +1,37 @@
package org.keycloak.audit.jpa;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.AuditProviderFactory;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JpaAuditProviderFactory implements AuditProviderFactory {
public static final String ID = "jpa";
private EntityManagerFactory emf;
@Override
public AuditProvider create() {
return new JpaAuditProvider(emf.createEntityManager());
}
@Override
public void init() {
emf = Persistence.createEntityManagerFactory("jpa-keycloak-audit-store");
}
@Override
public void close() {
emf.close();
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,100 @@
package org.keycloak.audit.jpa;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JpaEventQuery implements EventQuery {
private final EntityManager em;
private final CriteriaBuilder cb;
private final CriteriaQuery<EventEntity> cq;
private final Root<EventEntity> root;
private final ArrayList<Predicate> predicates;
private Integer firstResult;
private Integer maxResults;
public JpaEventQuery(EntityManager em) {
this.em = em;
cb = em.getCriteriaBuilder();
cq = cb.createQuery(EventEntity.class);
root = cq.from(EventEntity.class);
predicates = new ArrayList<Predicate>(4);
}
@Override
public EventQuery event(String event) {
predicates.add(cb.equal(root.get("event"), event));
return this;
}
@Override
public EventQuery realm(String realmId) {
predicates.add(cb.equal(root.get("realmId"), realmId));
return this;
}
@Override
public EventQuery client(String clientId) {
predicates.add(cb.equal(root.get("clientId"), clientId));
return this;
}
@Override
public EventQuery user(String userId) {
predicates.add(cb.equal(root.get("userId"), userId));
return this;
}
@Override
public EventQuery firstResult(int firstResult) {
this.firstResult = firstResult;
return this;
}
@Override
public EventQuery maxResults(int maxResults) {
this.maxResults = maxResults;
return this;
}
@Override
public List<Event> getResultList() {
if (!predicates.isEmpty()) {
cq.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
}
cq.orderBy(cb.asc(root.get("time")), cb.asc(root.get("id")));
TypedQuery<EventEntity> query = em.createQuery(cq);
if (firstResult != null) {
query.setFirstResult(firstResult);
}
if (maxResults != null) {
query.setMaxResults(maxResults);
}
List<Event> events = new LinkedList<Event>();
for (EventEntity e : query.getResultList()) {
events.add(JpaAuditProvider.convert(e));
}
return events;
}
}

View file

@ -0,0 +1 @@
org.keycloak.audit.jpa.JpaAuditProviderFactory

View file

@ -0,0 +1,15 @@
package org.keycloak.audit.jpa;
import org.keycloak.audit.tests.AbstractAuditProviderTest;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class JpaAuditProviderTest extends AbstractAuditProviderTest {
@Override
protected String getProviderId() {
return JpaAuditProviderFactory.ID;
}
}

View file

@ -0,0 +1,22 @@
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="jpa-keycloak-audit-store" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>org.keycloak.audit.jpa.EventEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
<property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
<property name="hibernate.connection.username" value="sa"/>
<property name="hibernate.connection.password" value=""/>
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
<property name="hibernate.show_sql" value="false" />
<property name="hibernate.format_sql" value="true" />
</properties>
</persistence-unit>
</persistence>

129
audit/mongo/pom.xml Executable file
View file

@ -0,0 +1,129 @@
<?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-mongo</artifactId>
<name>Keycloak Audit Mongo 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-audit-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-tests</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
</dependencies>
<properties>
<keycloak.audit.mongo.host>localhost</keycloak.audit.mongo.host>
<keycloak.audit.mongo.port>27018</keycloak.audit.mongo.port>
<keycloak.audit.mongo.db>keycloak</keycloak.audit.mongo.db>
<keycloak.audit.mongo.clearOnStartup>true</keycloak.audit.mongo.clearOnStartup>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<!-- Postpone tests to "integration-test" phase, so that we can bootstrap embedded mongo on 27018 before running tests -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>test</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemPropertyVariables>
<keycloak.audit.mongo.host>${keycloak.audit.mongo.host}</keycloak.audit.mongo.host>
<keycloak.audit.mongo.port>${keycloak.audit.mongo.port}</keycloak.audit.mongo.port>
<keycloak.audit.mongo.db>${keycloak.audit.mongo.db}</keycloak.audit.mongo.db>
<keycloak.audit.mongo.clearOnStartup>${keycloak.audit.mongo.clearOnStartup}</keycloak.audit.mongo.clearOnStartup>
</systemPropertyVariables>
<dependenciesToScan>
<dependency>org.keycloak:keycloak-model-tests</dependency>
</dependenciesToScan>
</configuration>
</execution>
<execution>
<id>default-test</id>
<configuration>
<skip>true</skip>
</configuration>
</execution>
</executions>
</plugin>
<!-- Embedded mongo -->
<plugin>
<groupId>com.github.joelittlejohn.embedmongo</groupId>
<artifactId>embedmongo-maven-plugin</artifactId>
<executions>
<execution>
<id>start-mongodb</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
<configuration>
<port>${keycloak.audit.mongo.port}</port>
<logging>file</logging>
<logFile>${project.build.directory}/mongodb.log</logFile>
</configuration>
</execution>
<execution>
<id>stop-mongodb</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,87 @@
package org.keycloak.audit.mongo;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class MongoAuditProvider implements AuditProvider {
private DBCollection audit;
public MongoAuditProvider(DBCollection audit) {
this.audit = audit;
}
@Override
public EventQuery createQuery() {
return new MongoEventQuery(audit);
}
@Override
public void clear() {
audit.remove(new BasicDBObject());
}
@Override
public void clear(long olderThan) {
audit.remove(new BasicDBObject("time", new BasicDBObject("$lt", olderThan)));
}
@Override
public void onEvent(Event event) {
audit.insert(convert(event));
}
@Override
public void close() {
}
static DBObject convert(Event o) {
BasicDBObject e = new BasicDBObject();
e.put("time", o.getTime());
e.put("event", o.getEvent());
e.put("realmId", o.getRealmId());
e.put("clientId", o.getClientId());
e.put("userId", o.getUserId());
e.put("ipAddress", o.getIpAddress());
e.put("error", o.getError());
BasicDBObject details = new BasicDBObject();
for (Map.Entry<String, String> entry : o.getDetails().entrySet()) {
details.put(entry.getKey(), entry.getValue());
}
e.put("details", details);
return e;
}
static Event convert(BasicDBObject o) {
Event e = new Event();
e.setTime(o.getLong("time"));
e.setEvent(o.getString("event"));
e.setRealmId(o.getString("realmId"));
e.setClientId(o.getString("clientId"));
e.setUserId(o.getString("userId"));
e.setIpAddress(o.getString("ipAddress"));
e.setError(o.getString("error"));
BasicDBObject d = (BasicDBObject) o.get("details");
Map<String, String> details = new HashMap<String, String>();
for (Object k : d.keySet()) {
details.put((String) k, d.getString((String) k));
}
e.setDetails(details);
return e;
}
}

View file

@ -0,0 +1,50 @@
package org.keycloak.audit.mongo;
import com.mongodb.DB;
import com.mongodb.MongoClient;
import com.mongodb.WriteConcern;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.AuditProviderFactory;
import java.net.UnknownHostException;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class MongoAuditProviderFactory implements AuditProviderFactory {
private static final String MONGO_HOST = "keycloak.audit.mongo.host";
private static final String MONGO_PORT = "keycloak.audit.mongo.port";
private static final String MONGO_DB_NAME = "keycloak.audit.mongo.db";
public static final String ID = "mongo";
private MongoClient client;
private DB db;
@Override
public AuditProvider create() {
return new MongoAuditProvider(db.getCollection("audit"));
}
@Override
public void init() {
try {
client = new MongoClient(System.getProperty(MONGO_HOST, "localhost"), Integer.parseInt(System.getProperty(MONGO_PORT, "27017")));
client.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
db = client.getDB(System.getProperty(MONGO_DB_NAME, "keycloak-audit"));
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
}
@Override
public void close() {
client.close();
}
@Override
public String getId() {
return ID;
}
}

View file

@ -0,0 +1,81 @@
package org.keycloak.audit.mongo;
import com.mongodb.BasicDBObject;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import org.keycloak.audit.Event;
import org.keycloak.audit.EventQuery;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class MongoEventQuery implements EventQuery {
private Integer firstResult;
private Integer maxResults;
private DBCollection audit;
private final BasicDBObject query;
public MongoEventQuery(DBCollection audit) {
this.audit = audit;
query = new BasicDBObject();
}
@Override
public EventQuery event(String event) {
query.put("event", event);
return this;
}
@Override
public EventQuery realm(String realmId) {
query.put("realmId", realmId);
return this;
}
@Override
public EventQuery client(String clientId) {
query.put("clientId", clientId);
return this;
}
@Override
public EventQuery user(String userId) {
query.put("userId", userId);
return this;
}
@Override
public EventQuery firstResult(int firstResult) {
this.firstResult = firstResult;
return this;
}
@Override
public EventQuery maxResults(int maxResults) {
this.maxResults = maxResults;
return this;
}
@Override
public List<Event> getResultList() {
DBCursor cur = audit.find(query);
if (firstResult != null) {
cur.skip(firstResult);
}
if (maxResults != null) {
cur.limit(maxResults);
}
List<Event> events = new LinkedList<Event>();
while (cur.hasNext()) {
events.add(MongoAuditProvider.convert((BasicDBObject) cur.next()));
}
return events;
}
}

View file

@ -0,0 +1 @@
org.keycloak.audit.mongo.MongoAuditProviderFactory

View file

@ -0,0 +1,15 @@
package org.keycloak.audit.mongo;
import org.keycloak.audit.tests.AbstractAuditProviderTest;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class MongoAuditProviderTest extends AbstractAuditProviderTest {
@Override
protected String getProviderId() {
return MongoAuditProviderFactory.ID;
}
}

View file

@ -19,5 +19,7 @@
<module>api</module>
<module>jpa</module>
<module>jboss-logging</module>
<module>mongo</module>
<module>tests</module>
</modules>
</project>

35
audit/tests/pom.xml Executable file
View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<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-tests</artifactId>
<name>Keycloak Audit Tests</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-audit-api</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,121 @@
package org.keycloak.audit.tests;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.audit.AuditProvider;
import org.keycloak.audit.AuditProviderFactory;
import org.keycloak.audit.Event;
import org.keycloak.provider.ProviderFactoryLoader;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public abstract class AbstractAuditProviderTest {
private AuditProviderFactory factory;
private AuditProvider provider;
@Before
public void before() {
ProviderFactoryLoader<AuditProviderFactory> loader = ProviderFactoryLoader.load(AuditProviderFactory.class);
factory = loader.find(getProviderId());
factory.init();
provider = factory.create();
}
protected abstract String getProviderId();
@After
public void after() {
provider.clear();
provider.close();
factory.close();
}
@Test
public void save() {
provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
}
@Test
public void query() {
provider.onEvent(create("event", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create("event2", "realmId", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create("event", "realmId2", "clientId", "userId", "127.0.0.1", "error"));
provider.onEvent(create("event", "realmId", "clientId2", "userId", "127.0.0.1", "error"));
provider.onEvent(create("event", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
provider.close();
provider = factory.create();
Assert.assertEquals(4, provider.createQuery().client("clientId").getResultList().size());
Assert.assertEquals(4, provider.createQuery().realm("realmId").getResultList().size());
Assert.assertEquals(4, provider.createQuery().event("event").getResultList().size());
Assert.assertEquals(4, provider.createQuery().user("userId").getResultList().size());
Assert.assertEquals(1, provider.createQuery().user("userId").event("event2").getResultList().size());
Assert.assertEquals(2, provider.createQuery().maxResults(2).getResultList().size());
Assert.assertEquals(1, provider.createQuery().firstResult(4).getResultList().size());
}
@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.close();
provider = factory.create();
provider.clear();
Assert.assertEquals(0, provider.createQuery().getResultList().size());
}
@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.close();
provider = factory.create();
provider.clear(System.currentTimeMillis() - 10000);
Assert.assertEquals(2, provider.createQuery().getResultList().size());
}
private Event create(String 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) {
Event e = new Event();
e.setTime(time);
e.setEvent(event);
e.setRealmId(realmId);
e.setClientId(clientId);
e.setUserId(userId);
e.setIpAddress(ipAddress);
e.setError(error);
Map<String, String> details = new HashMap<String, String>();
details.put("key1", "value1");
details.put("key2", "value2");
e.setDetails(details);
return e;
}
}

View file

@ -0,0 +1,10 @@
package org.keycloak.provider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface Provider {
public void close();
}

View file

@ -0,0 +1,16 @@
package org.keycloak.provider;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface ProviderFactory<T extends Provider> {
public T create();
public void init();
public void close();
public String getId();
}

View file

@ -0,0 +1,88 @@
package org.keycloak.provider;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ProviderFactoryLoader<P extends ProviderFactory> implements Iterable<P> {
private ServiceLoader<P> serviceLoader;
private ProviderFactoryLoader(ServiceLoader<P> serviceLoader) {
this.serviceLoader = serviceLoader;
}
public static <P extends ProviderFactory> ProviderFactoryLoader<P> load(Class<P> service) {
return new ProviderFactoryLoader(ServiceLoader.load(service));
}
public static <P extends ProviderFactory> ProviderFactoryLoader<P> load(Class<P> service, ClassLoader loader) {
return new ProviderFactoryLoader(ServiceLoader.load(service, loader));
}
public P find(String id) {
Iterator<P> itr = iterator();
while (itr.hasNext()) {
P p = itr.next();
if (p.getId() != null && p.getId().equals(id)) {
return p;
}
}
return null;
}
@Override
public Iterator<P> iterator() {
return new ProviderFactoryIterator(serviceLoader.iterator());
}
public void close() {
}
private static class ProviderFactoryIterator<P> implements Iterator<P> {
private Iterator<P> itr;
private P next;
private ProviderFactoryIterator(Iterator<P> itr) {
this.itr = itr;
setNext();
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public P next() {
P n = next;
setNext();
return n;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private void setNext() {
next = null;
while (itr.hasNext()) {
if (itr.hasNext()) {
P n = itr.next();
if (!System.getProperties().containsKey(n.getClass().getName() + ".disabled")) {
next = n;
return;
}
}
}
}
}
}

View file

@ -48,6 +48,21 @@
<artifactId>keycloak-model-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-jboss-logging</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-social-core</artifactId>
@ -182,6 +197,11 @@
<artifactId>keycloak-model-mongo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-mongo</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>

View file

@ -9,6 +9,7 @@ import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
import org.keycloak.audit.AuditListener;
import org.keycloak.audit.AuditListenerFactory;
import org.keycloak.audit.Details;
import org.keycloak.audit.Event;
import org.keycloak.models.ClientModel;
@ -31,7 +32,7 @@ import java.util.concurrent.TimeUnit;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AssertEvents implements TestRule, AuditListener{
public class AssertEvents implements TestRule, AuditListenerFactory {
private static final Logger log = Logger.getLogger(AssertEvents.class);
@ -57,11 +58,6 @@ public class AssertEvents implements TestRule, AuditListener{
return "assert-events";
}
@Override
public void onEvent(Event event) {
events.add(event);
}
@Override
public Statement apply(final Statement base, org.junit.runner.Description description) {
return new Statement() {
@ -165,6 +161,28 @@ public class AssertEvents implements TestRule, AuditListener{
return new ExpectedEvent().realm(DEFAULT_REALM).client(DEFAULT_CLIENT_ID).user(keycloak.getUser(DEFAULT_REALM, DEFAULT_USERNAME).getId()).ipAddress(DEFAULT_IP_ADDRESS).event(event);
}
@Override
public AuditListener create() {
return new AuditListener() {
@Override
public void onEvent(Event event) {
events.add(event);
}
@Override
public void close() {
}
};
}
@Override
public void init() {
}
@Override
public void close() {
}
public static class ExpectedEvent {
private Event expected = new Event();
private Matcher<String> userId;