keycloak-aplcache

Merge pull request #315 from stianst/master Added audit

4/3/2014 2:24:11 PM

Changes

Details

diff --git a/audit/api/src/main/java/org/keycloak/audit/Audit.java b/audit/api/src/main/java/org/keycloak/audit/Audit.java
index 682ceb4..4cbd4f7 100644
--- a/audit/api/src/main/java/org/keycloak/audit/Audit.java
+++ b/audit/api/src/main/java/org/keycloak/audit/Audit.java
@@ -20,24 +20,15 @@ public class Audit {
     private List<AuditListener> listeners;
     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(loader.find(id).create());
-            }
-        }
+    public Audit(List<AuditListener> listeners, RealmModel realm, String ipAddress) {
+        this.listeners = listeners;
+        this.event = new Event();
 
-        return new Audit(listeners, new Event()).realm(realm).ipAddress(ipAddress);
+        realm(realm);
+        ipAddress(ipAddress);
     }
 
-    private Audit(List<AuditListener> listeners, Event event) {
-        this.listeners = listeners;
-        this.event = event;
+    Audit() {
     }
 
     public Audit realm(RealmModel realm) {
@@ -113,7 +104,10 @@ public class Audit {
     }
 
     public Audit clone() {
-        return new Audit(listeners, event.clone());
+        Audit clone = new Audit();
+        clone.listeners = listeners;
+        clone.event = event.clone();
+        return clone;
     }
 
     public Audit reset() {
diff --git a/audit/api/src/main/java/org/keycloak/audit/EventQuery.java b/audit/api/src/main/java/org/keycloak/audit/EventQuery.java
index 75e07c0..339bc64 100644
--- a/audit/api/src/main/java/org/keycloak/audit/EventQuery.java
+++ b/audit/api/src/main/java/org/keycloak/audit/EventQuery.java
@@ -7,7 +7,7 @@ import java.util.List;
  */
 public interface EventQuery {
 
-    public EventQuery event(String event);
+    public EventQuery event(String... events);
 
     public EventQuery realm(String realmId);
 
diff --git a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java
index 420bd67..62322fb 100644
--- a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java
+++ b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java
@@ -31,4 +31,9 @@ public class JBossLoggingAuditListenerFactory implements AuditListenerFactory {
         return ID;
     }
 
+    @Override
+    public boolean lazyLoad() {
+        return false;
+    }
+
 }
diff --git a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProviderFactory.java b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProviderFactory.java
index 5fb7a83..7d84b26 100644
--- a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProviderFactory.java
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProviderFactory.java
@@ -47,4 +47,9 @@ public class MongoAuditProviderFactory implements AuditProviderFactory {
         return ID;
     }
 
+    @Override
+    public boolean lazyLoad() {
+        return true;
+    }
+
 }
diff --git a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
index e425455..5c75e63 100644
--- a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
@@ -25,8 +25,8 @@ public class MongoEventQuery implements EventQuery {
     }
 
     @Override
-    public EventQuery event(String event) {
-        query.put("event", event);
+    public EventQuery event(String... events) {
+        query.put("event", new BasicDBObject("$in", events));
         return this;
     }
 
@@ -62,7 +62,7 @@ public class MongoEventQuery implements EventQuery {
 
     @Override
     public List<Event> getResultList() {
-        DBCursor cur = audit.find(query);
+        DBCursor cur = audit.find(query).sort(new BasicDBObject("time", -1));
         if (firstResult != null) {
             cur.skip(firstResult);
         }
diff --git a/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java b/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
index c5d1bb7..c3ebdaa 100644
--- a/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
+++ b/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
@@ -7,6 +7,7 @@ import org.junit.Test;
 import org.keycloak.audit.AuditProvider;
 import org.keycloak.audit.AuditProviderFactory;
 import org.keycloak.audit.Event;
+import org.keycloak.provider.ProviderFactory;
 import org.keycloak.provider.ProviderFactoryLoader;
 
 import java.util.HashMap;
@@ -17,12 +18,12 @@ import java.util.Map;
  */
 public abstract class AbstractAuditProviderTest {
 
-    private AuditProviderFactory factory;
+    private ProviderFactory<AuditProvider> factory;
     private AuditProvider provider;
 
     @Before
     public void before() {
-        ProviderFactoryLoader<AuditProviderFactory> loader = ProviderFactoryLoader.load(AuditProviderFactory.class);
+        ProviderFactoryLoader<AuditProvider> loader = ProviderFactoryLoader.create(AuditProviderFactory.class);
         factory = loader.find(getProviderId());
         factory.init();
 
@@ -45,10 +46,13 @@ public abstract class AbstractAuditProviderTest {
 
     @Test
     public void query() {
+        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("event2", "realmId", "clientId", "userId", "127.0.0.1", "error"));
+        provider.onEvent(create(newest, "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(oldest, "event", "realmId", "clientId2", "userId", "127.0.0.1", "error"));
         provider.onEvent(create("event", "realmId", "clientId", "userId2", "127.0.0.1", "error"));
 
         provider.close();
@@ -57,12 +61,16 @@ public abstract class AbstractAuditProviderTest {
         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(5, provider.createQuery().event("event", "event2").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());
+
+        Assert.assertEquals(newest, provider.createQuery().maxResults(1).getResultList().get(0).getTime());
+        Assert.assertEquals(oldest, provider.createQuery().firstResult(4).maxResults(1).getResultList().get(0).getTime());
     }
 
     @Test
diff --git a/core/src/main/java/org/keycloak/provider/ProviderFactory.java b/core/src/main/java/org/keycloak/provider/ProviderFactory.java
index 720538d..ca8a19b 100644
--- a/core/src/main/java/org/keycloak/provider/ProviderFactory.java
+++ b/core/src/main/java/org/keycloak/provider/ProviderFactory.java
@@ -13,4 +13,6 @@ public interface ProviderFactory<T extends Provider> {
 
     public String getId();
 
+    public boolean lazyLoad();
+
 }
diff --git a/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java b/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
index 51b00dc..fdbb171 100644
--- a/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
+++ b/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
@@ -1,88 +1,100 @@
 package org.keycloak.provider;
 
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.ServiceLoader;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class ProviderFactoryLoader<P extends ProviderFactory> implements Iterable<P> {
+public class ProviderFactoryLoader<T extends Provider> implements Iterable<ProviderFactory<T>> {
 
-    private ServiceLoader<P> serviceLoader;
+    private Map<String, ProviderFactory<T>> factories = new HashMap<String, ProviderFactory<T>>();
 
-    private ProviderFactoryLoader(ServiceLoader<P> serviceLoader) {
-        this.serviceLoader = serviceLoader;
+    private ProviderFactoryLoader(ServiceLoader<? extends ProviderFactory> serviceLoader) {
+        for (ProviderFactory p : serviceLoader) {
+            if (!System.getProperties().containsKey(p.getClass().getName() + ".disabled")) {
+                if (p.lazyLoad()) {
+                    p = new LazyProviderFactory(p);
+                }
+                factories.put(p.getId(), p);
+            }
+        }
     }
 
-    public static <P extends ProviderFactory> ProviderFactoryLoader<P> load(Class<P> service) {
+    public static ProviderFactoryLoader create(Class<? extends ProviderFactory> service) {
         return new ProviderFactoryLoader(ServiceLoader.load(service));
     }
 
-    public static <P extends ProviderFactory> ProviderFactoryLoader<P> load(Class<P> service, ClassLoader loader) {
+    public static ProviderFactoryLoader create(Class<? extends ProviderFactory> 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;
+    public ProviderFactory find(String id) {
+        return factories.get(id);
     }
 
     @Override
-    public Iterator<P> iterator() {
-        return new ProviderFactoryIterator(serviceLoader.iterator());
+    public Iterator<ProviderFactory<T>> iterator() {
+        return factories.values().iterator();
     }
 
-    public void close() {
+    public Set<String> providerIds() {
+        return factories.keySet();
+    }
+
+    public void init() {
+        for (ProviderFactory p : factories.values()) {
+            p.init();
+        }
+    }
 
+    public void close() {
+        for (ProviderFactory p : factories.values()) {
+            p.close();
+        }
     }
 
-    private static class ProviderFactoryIterator<P> implements Iterator<P> {
+    private class LazyProviderFactory<T extends Provider> implements ProviderFactory<T> {
 
-        private Iterator<P> itr;
+        private volatile boolean initialized = false;
 
-        private P next;
+        private ProviderFactory<T> factory;
 
-        private ProviderFactoryIterator(Iterator<P> itr) {
-            this.itr = itr;
-            setNext();
+        private LazyProviderFactory(ProviderFactory<T> factory) {
+            this.factory = factory;
         }
 
         @Override
-        public boolean hasNext() {
-            return next != null;
+        public synchronized T create() {
+            if (!initialized) {
+                factory.init();
+                initialized = true;
+            }
+            return factory.create();
         }
 
         @Override
-        public P next() {
-            P n = next;
-            setNext();
-            return n;
+        public void init() {
+            // do nothing
         }
 
         @Override
-        public void remove() {
-            throw new UnsupportedOperationException();
+        public void close() {
+            factory.close();
         }
 
-        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;
-                    }
-                }
-            }
+        @Override
+        public String getId() {
+            return factory.getId();
         }
 
+        @Override
+        public boolean lazyLoad() {
+            return false;
+        }
     }
 
 }
diff --git a/forms/account-api/pom.xml b/forms/account-api/pom.xml
index e153d52..1ad638a 100755
--- a/forms/account-api/pom.xml
+++ b/forms/account-api/pom.xml
@@ -25,6 +25,11 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.resteasy</groupId>
             <artifactId>jaxrs-api</artifactId>
         </dependency>
diff --git a/forms/account-api/src/main/java/org/keycloak/account/Account.java b/forms/account-api/src/main/java/org/keycloak/account/Account.java
index 060b8a5..f92b161 100644
--- a/forms/account-api/src/main/java/org/keycloak/account/Account.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/Account.java
@@ -1,30 +1,35 @@
 package org.keycloak.account;
 
+import org.keycloak.audit.Event;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
+import java.util.List;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
 public interface Account {
 
-    public Response createResponse(AccountPages page);
+    Response createResponse(AccountPages page);
 
-    public Account setError(String message);
+    Account setError(String message);
 
-    public Account setSuccess(String message);
+    Account setSuccess(String message);
 
-    public Account setWarning(String message);
+    Account setWarning(String message);
 
-    public Account setUser(UserModel user);
+    Account setUser(UserModel user);
 
-    public Account setStatus(Response.Status status);
+    Account setStatus(Response.Status status);
 
-    public Account setRealm(RealmModel realm);
+    Account setRealm(RealmModel realm);
 
-    public Account setReferrer(String[] referrer);
+    Account setReferrer(String[] referrer);
 
+    Account setEvents(List<Event> events);
+
+    Account setFeatures(boolean social, boolean audit);
 }
diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java b/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java
index a3e60d2..ceeca40 100644
--- a/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountPages.java
@@ -5,6 +5,6 @@ package org.keycloak.account;
  */
 public enum AccountPages {
 
-    ACCOUNT, PASSWORD, TOTP, SOCIAL;
+    ACCOUNT, PASSWORD, TOTP, SOCIAL, LOG;
 
 }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
index 143aa1c..53f43d4 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccount.java
@@ -5,10 +5,13 @@ import org.keycloak.account.Account;
 import org.keycloak.account.AccountPages;
 import org.keycloak.account.freemarker.model.AccountBean;
 import org.keycloak.account.freemarker.model.AccountSocialBean;
+import org.keycloak.account.freemarker.model.FeaturesBean;
+import org.keycloak.account.freemarker.model.LogBean;
 import org.keycloak.account.freemarker.model.MessageBean;
 import org.keycloak.account.freemarker.model.ReferrerBean;
 import org.keycloak.account.freemarker.model.TotpBean;
 import org.keycloak.account.freemarker.model.UrlBean;
+import org.keycloak.audit.Event;
 import org.keycloak.freemarker.FreeMarkerException;
 import org.keycloak.freemarker.FreeMarkerUtil;
 import org.keycloak.freemarker.Theme;
@@ -23,6 +26,7 @@ import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.net.URI;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
@@ -37,6 +41,9 @@ public class FreeMarkerAccount implements Account {
     private Response.Status status = Response.Status.OK;
     private RealmModel realm;
     private String[] referrer;
+    private List<Event> events;
+    private boolean social;
+    private boolean audit;
 
     public static enum MessageType {SUCCESS, WARNING, ERROR}
 
@@ -88,9 +95,7 @@ public class FreeMarkerAccount implements Account {
 
         attributes.put("url", new UrlBean(realm, theme, baseUri));
 
-        if (realm.isSocial()) {
-            attributes.put("isSocialRealm", true);
-        }
+        attributes.put("features", new FeaturesBean(social, audit));
 
         switch (page) {
             case ACCOUNT:
@@ -102,6 +107,8 @@ public class FreeMarkerAccount implements Account {
             case SOCIAL:
                 attributes.put("social", new AccountSocialBean(realm, user, uriInfo.getBaseUri()));
                 break;
+            case LOG:
+                attributes.put("log", new LogBean(events));
         }
 
         try {
@@ -157,4 +164,17 @@ public class FreeMarkerAccount implements Account {
         this.referrer = referrer;
         return this;
     }
+
+    @Override
+    public Account setEvents(List<Event> events) {
+        this.events = events;
+        return this;
+    }
+
+    @Override
+    public Account setFeatures(boolean social, boolean audit) {
+        this.social = social;
+        this.audit = audit;
+        return this;
+    }
 }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java
new file mode 100644
index 0000000..6f8158b
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/FeaturesBean.java
@@ -0,0 +1,24 @@
+package org.keycloak.account.freemarker.model;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FeaturesBean {
+
+    private final boolean social;
+    private final boolean log;
+
+    public FeaturesBean(boolean social, boolean log) {
+        this.social = social;
+        this.log = log;
+    }
+
+    public boolean isSocial() {
+        return social;
+    }
+
+    public boolean isLog() {
+        return log;
+    }
+
+}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/LogBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/LogBean.java
new file mode 100644
index 0000000..f138f8d
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/LogBean.java
@@ -0,0 +1,53 @@
+package org.keycloak.account.freemarker.model;
+
+import org.keycloak.audit.Event;
+
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LogBean {
+
+    private List<EventBean> events;
+
+    public LogBean(List<Event> events) {
+        this.events = new LinkedList<EventBean>();
+        for (Event e : events) {
+            this.events.add(new EventBean(e));
+        }
+    }
+
+    public List<EventBean> getEvents() {
+        return events;
+    }
+
+    public static 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().replace('_', ' ');
+        }
+
+        public String getClient() {
+            return event.getClientId();
+        }
+
+        public String getIpAddress() {
+            return event.getIpAddress();
+        }
+
+    }
+
+}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
index 53dde7c..e626ddf 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
@@ -41,6 +41,10 @@ public class UrlBean {
         return Urls.accountTotpPage(baseURI, realm).toString();
     }
 
+    public String getLogUrl() {
+        return Urls.accountLogPage(baseURI, realm).toString();
+    }
+
     public String getTotpRemoveUrl() {
         return Urls.accountTotpRemove(baseURI, realm).toString();
     }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java
index 5a63ef5..f9d051c 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/Templates.java
@@ -17,6 +17,8 @@ public class Templates {
                 return "totp.ftl";
             case SOCIAL:
                 return "social.ftl";
+            case LOG:
+                return "log.ftl";
             default:
                 throw new IllegalArgumentException();
         }
diff --git a/forms/common-themes/src/main/resources/theme/account/base/log.ftl b/forms/common-themes/src/main/resources/theme/account/base/log.ftl
new file mode 100644
index 0000000..9752317
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/base/log.ftl
@@ -0,0 +1,32 @@
+<#import "template.ftl" as layout>
+<@layout.mainLayout active='log' bodyClass='log'; section>
+
+    <div class="row">
+        <div class="col-md-10">
+            <h2>Account Log</h2>
+        </div>
+    </div>
+
+    <table class="table">
+        <thead>
+        <tr>
+            <td>Date</td>
+            <td>Event</td>
+            <td>IP</td>
+            <td>Client</td>
+        </tr>
+        </thead>
+
+        <tbody>
+        <#list log.events as event>
+            <tr>
+                <td>${event.date?datetime}</td>
+                <td>${event.event}</td>
+                <td>${event.ipAddress}</td>
+                <td>${event.client}</td
+            </tr>
+        </#list>
+        </tbody>
+    </table>
+
+</@layout.mainLayout>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/account/base/template.ftl b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
index d6a2f5e..6a39817 100644
--- a/forms/common-themes/src/main/resources/theme/account/base/template.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
@@ -42,7 +42,8 @@
                 <li class="<#if active=='account'>active</#if>"><a href="${url.accountUrl}">Account</a></li>
                 <li class="<#if active=='password'>active</#if>"><a href="${url.passwordUrl}">Password</a></li>
                 <li class="<#if active=='totp'>active</#if>"><a href="${url.totpUrl}">Authenticator</a></li>
-                <#if isSocialRealm?has_content><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Social</a></li></#if>
+                <#if features.social><li class="<#if active=='social'>active</#if>"><a href="${url.socialUrl}">Social</a></li></#if>
+                <#if features.log><li class="<#if active=='log'>active</#if>"><a href="${url.logUrl}">Log</a></li></#if>
             </ul>
         </div>
 
diff --git a/model/api/src/main/java/org/keycloak/models/Config.java b/model/api/src/main/java/org/keycloak/models/Config.java
index fccf84f..7851ea8 100644
--- a/model/api/src/main/java/org/keycloak/models/Config.java
+++ b/model/api/src/main/java/org/keycloak/models/Config.java
@@ -12,6 +12,8 @@ public class Config {
 
     public static final String MODEL_PROVIDER_KEY = "keycloak.model";
 
+    public static final String MODEL_AUDIT_KEY = "keycloak.audit";
+
     public static final String THEME_BASE_KEY = "keycloak.theme.base";
     public static final String THEME_BASE_DEFAULT = "base";
     public static final String THEME_DEFAULT_KEY = "keycloak.theme.default";
@@ -27,6 +29,14 @@ public class Config {
         System.setProperty(ADMIN_REALM_KEY, realm);
     }
 
+    public static String getAuditProvider() {
+        return System.getProperty(MODEL_PROVIDER_KEY, "jpa");
+    }
+
+    public static void setAuditProvider(String provider) {
+        System.setProperty(MODEL_PROVIDER_KEY, provider);
+    }
+
     public static String getModelProvider() {
         return System.getProperty(MODEL_PROVIDER_KEY);
     }
diff --git a/services/src/main/java/org/keycloak/services/DefaultProviderSession.java b/services/src/main/java/org/keycloak/services/DefaultProviderSession.java
new file mode 100755
index 0000000..f9e2948
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/DefaultProviderSession.java
@@ -0,0 +1,49 @@
+package org.keycloak.services;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultProviderSession implements ProviderSession {
+    private DefaultProviderSessionFactory factory;
+    private Map<Integer, Provider> providers = new HashMap<Integer, Provider>();
+
+    public DefaultProviderSession(DefaultProviderSessionFactory factory) {
+        this.factory = factory;
+    }
+
+    public <T extends Provider> T getProvider(Class<T> clazz) {
+        String id = factory.getDefaultProvider(clazz);
+        return id != null ? getProvider(clazz, id) : null;
+    }
+
+    public <T extends Provider> T getProvider(Class<T> clazz, String id) {
+        Integer hash = clazz.hashCode() + id.hashCode();
+        T provider = (T) providers.get(hash);
+        if (provider == null) {
+            ProviderFactory<T> providerFactory = factory.getProviderFactory(clazz, id);
+            if (providerFactory != null) {
+                provider = providerFactory.create();
+                providers.put(hash, provider);
+            }
+        }
+        return provider;
+    }
+
+    public <T extends Provider> Set<String> listProviderIds(Class<T> clazz) {
+        return factory.providerIds(clazz);
+    }
+
+    public void close() {
+        for (Provider p : providers.values()) {
+            p.close();
+        }
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/DefaultProviderSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultProviderSessionFactory.java
new file mode 100755
index 0000000..6728559
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/DefaultProviderSessionFactory.java
@@ -0,0 +1,69 @@
+package org.keycloak.services;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.ProviderFactoryLoader;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class DefaultProviderSessionFactory implements ProviderSessionFactory {
+
+    private Map<Class<? extends Provider>, ProviderFactoryLoader> loaders = new HashMap<Class<? extends Provider>, ProviderFactoryLoader>();
+    private Map<Class<? extends Provider>, String> defaultFactories = new HashMap<Class<? extends Provider>, String>();
+
+    public ProviderSession createSession() {
+        return new DefaultProviderSession(this);
+    }
+
+    public void close() {
+        for (ProviderFactoryLoader loader : loaders.values()) {
+            loader.close();
+        }
+    }
+
+    public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
+        String id = defaultFactories.get(clazz);
+        if (id == null) {
+            return null;
+        }
+        return getProviderFactory(clazz, id);
+    }
+
+    public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id) {
+        ProviderFactoryLoader loader = getLoader(clazz);
+        return loader != null ? loader.find(id) : null;
+    }
+
+    public Set<String> providerIds(Class<? extends Provider> clazz) {
+        ProviderFactoryLoader loader = getLoader(clazz);
+        return loader != null ? loader.providerIds() : null;
+    }
+
+    public String getDefaultProvider(Class<? extends Provider> clazz) {
+        return defaultFactories.get(clazz);
+    }
+
+    public void registerLoader(Class<? extends Provider> clazz, ProviderFactoryLoader loader) {
+        loaders.put(clazz, loader);
+
+    }
+
+    public void registerLoader(Class<? extends Provider> clazz, ProviderFactoryLoader loader, String defaultProvider) {
+        loaders.put(clazz, loader);
+        defaultFactories.put(clazz, defaultProvider);
+
+    }
+
+    public void init() {
+        for (ProviderFactoryLoader l : loaders.values()) {
+            l.init();
+        }
+    }
+
+    private <T extends Provider> ProviderFactoryLoader getLoader(Class<T> clazz) {
+        return loaders.get(clazz);
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
index 3f859c2..54338ca 100755
--- a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
+++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java
@@ -4,6 +4,8 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.services.ProviderSession;
+import org.keycloak.services.ProviderSessionFactory;
 import org.keycloak.util.KeycloakRegistry;
 
 import javax.servlet.Filter;
@@ -26,6 +28,11 @@ public class KeycloakSessionServletFilter implements Filter {
 
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
+        ProviderSessionFactory providerSessionFactory = (ProviderSessionFactory) servletRequest.getServletContext().getAttribute(ProviderSessionFactory.class.getName());
+        ProviderSession providerSession = providerSessionFactory.createSession();
+
+        ResteasyProviderFactory.pushContext(ProviderSession.class, providerSession);
+
         KeycloakRegistry registry = (KeycloakRegistry)servletRequest.getServletContext().getAttribute(KeycloakRegistry.class.getName());
         ResteasyProviderFactory.pushContext(KeycloakRegistry.class, registry);
         KeycloakSessionFactory factory = registry.getService(KeycloakSessionFactory.class);
@@ -53,6 +60,7 @@ public class KeycloakSessionServletFilter implements Filter {
             throw ex;
         } finally {
             session.close();
+            providerSession.close();
             ResteasyProviderFactory.clearContextData();
         }
 
diff --git a/services/src/main/java/org/keycloak/services/ProviderSession.java b/services/src/main/java/org/keycloak/services/ProviderSession.java
new file mode 100755
index 0000000..100dc54
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/ProviderSession.java
@@ -0,0 +1,20 @@
+package org.keycloak.services;
+
+import org.keycloak.provider.Provider;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderSession {
+
+    <T extends Provider> T getProvider(Class<T> clazz);
+
+    <T extends Provider> T getProvider(Class<T> clazz, String id);
+
+    <T extends Provider> Set<String> listProviderIds(Class<T> clazz);
+
+    void close();
+
+}
diff --git a/services/src/main/java/org/keycloak/services/ProviderSessionFactory.java b/services/src/main/java/org/keycloak/services/ProviderSessionFactory.java
new file mode 100755
index 0000000..a1c5707
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/ProviderSessionFactory.java
@@ -0,0 +1,28 @@
+package org.keycloak.services;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.ProviderFactoryLoader;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderSessionFactory {
+
+    ProviderSession createSession();
+
+    void close();
+
+    <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz);
+
+    <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id);
+
+    Set<String> providerIds(Class<? extends Provider> clazz);
+
+    void init();
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index b937674..9f47d7c 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -28,13 +28,22 @@ import org.keycloak.account.Account;
 import org.keycloak.account.AccountLoader;
 import org.keycloak.account.AccountPages;
 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.jaxrs.JaxrsOAuthClient;
-import org.keycloak.models.*;
+import org.keycloak.models.AccountRoles;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.representations.idm.CredentialRepresentation;
-import org.keycloak.services.managers.AccessCodeEntry;
+import org.keycloak.services.ProviderSession;
 import org.keycloak.services.managers.AppAuthManager;
 import org.keycloak.services.managers.Auth;
 import org.keycloak.services.managers.ModelToRepresentation;
@@ -51,8 +60,23 @@ import org.keycloak.spi.authentication.AuthProviderStatus;
 import org.keycloak.spi.authentication.AuthenticationProviderException;
 import org.keycloak.spi.authentication.AuthenticationProviderManager;
 
-import javax.ws.rs.*;
-import javax.ws.rs.core.*;
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.ForbiddenException;
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.core.Variant;
 import java.net.URI;
 import java.util.List;
 import java.util.UUID;
@@ -77,10 +101,16 @@ public class AccountService {
     @Context
     private UriInfo uriInfo;
 
+    @Context
+    private ProviderSession providers;
+
     private final AppAuthManager authManager;
     private final ApplicationModel application;
     private Audit audit;
     private final SocialRequestManager socialRequestManager;
+    private Account account;
+    private Auth auth;
+    private AuditProvider auditProvider;
 
     public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager, SocialRequestManager socialRequestManager, Audit audit) {
         this.realm = realm;
@@ -90,23 +120,30 @@ public class AccountService {
         this.socialRequestManager = socialRequestManager;
     }
 
+    public void init() {
+        auditProvider = providers.getProvider(AuditProvider.class);
+
+        account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setFeatures(realm.isSocial(), auditProvider != null);
+
+        auth = authManager.authenticate(realm, headers);
+        if (auth != null) {
+            account.setUser(auth.getUser());
+        }
+    }
+
     public static UriBuilder accountServiceBaseUrl(UriInfo uriInfo) {
         UriBuilder base = uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(RealmsResource.class, "getAccountService");
         return base;
     }
 
-
     private Response forwardToPage(String path, AccountPages page) {
-        Auth auth = getAuth(false);
         if (auth != null) {
             try {
-                require(auth, AccountRoles.MANAGE_ACCOUNT);
+                require(AccountRoles.MANAGE_ACCOUNT);
             } catch (ForbiddenException e) {
                 return Flows.forms(realm, request, uriInfo).setError("No access").createErrorPage();
             }
 
-            Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
-
             String[] referrer = getReferrer();
             if (referrer != null) {
                 account.setReferrer(referrer);
@@ -131,8 +168,7 @@ public class AccountService {
         if (types.contains(MediaType.WILDCARD_TYPE) || (types.contains(MediaType.TEXT_HTML_TYPE))) {
             return forwardToPage(null, AccountPages.ACCOUNT);
         } else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
-            Auth auth = getAuth(true);
-            requireOneOf(auth, AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
+            requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
 
             return Cors.add(request, Response.ok(ModelToRepresentation.toRepresentation(auth.getUser()))).auth().allowedOrigins(auth.getClient()).build();
         } else {
@@ -158,17 +194,24 @@ public class AccountService {
         return forwardToPage("social", AccountPages.SOCIAL);
     }
 
+    @Path("log")
+    @GET
+    public Response logPage() {
+        if (auth != null) {
+            List<Event> events = auditProvider.createQuery().user(auth.getUser().getId()).maxResults(20).getResultList();
+            account.setEvents(events);
+        }
+        return forwardToPage("log", AccountPages.LOG);
+    }
+
     @Path("/")
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
-        Auth auth = getAuth(true);
-        require(auth, AccountRoles.MANAGE_ACCOUNT);
+        require(AccountRoles.MANAGE_ACCOUNT);
 
         UserModel user = auth.getUser();
 
-        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
-
         String error = Validation.validateUpdateProfileForm(formData);
         if (error != null) {
             return account.setError(error).createResponse(AccountPages.ACCOUNT);
@@ -196,15 +239,13 @@ public class AccountService {
     @Path("totp-remove")
     @GET
     public Response processTotpRemove() {
-        Auth auth = getAuth(true);
-        require(auth, AccountRoles.MANAGE_ACCOUNT);
+        require(AccountRoles.MANAGE_ACCOUNT);
 
         UserModel user = auth.getUser();
         user.setTotp(false);
 
         audit.event(Events.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
 
-        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
         return account.setSuccess("successTotpRemoved").createResponse(AccountPages.TOTP);
     }
 
@@ -212,16 +253,13 @@ public class AccountService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
-        Auth auth = getAuth(true);
-        require(auth, AccountRoles.MANAGE_ACCOUNT);
+        require(AccountRoles.MANAGE_ACCOUNT);
 
         UserModel user = auth.getUser();
 
         String totp = formData.getFirst("totp");
         String totpSecret = formData.getFirst("totpSecret");
 
-        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
-
         if (Validation.isEmpty(totp)) {
             return account.setError(Messages.MISSING_TOTP).createResponse(AccountPages.TOTP);
         } else if (!new TimeBasedOTP().validate(totp, totpSecret.getBytes())) {
@@ -244,13 +282,10 @@ public class AccountService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
-        Auth auth = getAuth(true);
-        require(auth, AccountRoles.MANAGE_ACCOUNT);
+        require(AccountRoles.MANAGE_ACCOUNT);
 
         UserModel user = auth.getUser();
 
-        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
-
         String password = formData.getFirst("password");
         String passwordNew = formData.getFirst("password-new");
         String passwordConfirm = formData.getFirst("password-confirm");
@@ -286,12 +321,9 @@ public class AccountService {
     @GET
     public Response processSocialUpdate(@QueryParam("action") String action,
                                         @QueryParam("provider_id") String providerId) {
-        Auth auth = getAuth(true);
-        require(auth, AccountRoles.MANAGE_ACCOUNT);
+        require(AccountRoles.MANAGE_ACCOUNT);
         UserModel user = auth.getUser();
 
-        Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
-
         if (Validation.isEmpty(providerId)) {
             return account.setError(Messages.MISSING_SOCIAL_PROVIDER).createResponse(AccountPages.SOCIAL);
         }
@@ -426,14 +458,6 @@ public class AccountService {
         return oauth.redirect(uriInfo, accountUri.toString());
     }
 
-    private Auth getAuth(boolean error) {
-        Auth auth = authManager.authenticate(realm, headers);
-        if (auth == null && error) {
-            throw new ForbiddenException();
-        }
-        return auth;
-    }
-
     private String[] getReferrer() {
         String referrer = uriInfo.getQueryParameters().getFirst("referrer");
         if (referrer == null) {
@@ -467,13 +491,21 @@ public class AccountService {
         return null;
     }
 
-    public void require(Auth auth, String role) {
+    public void require(String role) {
+        if (auth == null) {
+            throw new ForbiddenException();
+        }
+
         if (!auth.hasAppRole(application.getName(), role)) {
             throw new ForbiddenException();
         }
     }
 
-    public void requireOneOf(Auth auth, String... roles) {
+    public void requireOneOf(String... roles) {
+        if (auth == null) {
+            throw new ForbiddenException();
+        }
+
         if (!auth.hasOneOfAppRole(application.getName(), roles)) {
             throw new ForbiddenException();
         }
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index 4147ada..55137c4 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -74,6 +74,10 @@ public class Urls {
         return accountBase(baseUri).path(AccountService.class, "processTotpRemove").build(realmId);
     }
 
+    public static URI accountLogPage(URI baseUri, String realmId) {
+        return accountBase(baseUri).path(AccountService.class, "logPage").build(realmId);
+    }
+
     public static URI accountLogout(URI baseUri, String realmId) {
         return accountBase(baseUri).path(AccountService.class, "logout").build(realmId);
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 977d7e9..31cc1e8 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -2,8 +2,16 @@ package org.keycloak.services.resources;
 
 import org.jboss.resteasy.logging.Logger;
 import org.keycloak.SkeletonKeyContextResolver;
+import org.keycloak.audit.AuditListener;
+import org.keycloak.audit.AuditListenerFactory;
+import org.keycloak.audit.AuditProvider;
+import org.keycloak.audit.AuditProviderFactory;
+import org.keycloak.models.Config;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.ModelProvider;
+import org.keycloak.provider.ProviderFactoryLoader;
+import org.keycloak.services.DefaultProviderSessionFactory;
+import org.keycloak.services.ProviderSessionFactory;
 import org.keycloak.util.KeycloakRegistry;
 import org.keycloak.services.managers.ApplianceBootstrap;
 import org.keycloak.services.managers.SocialRequestManager;
@@ -41,6 +49,8 @@ public class KeycloakApplication extends Application {
         context.setAttribute(KeycloakRegistry.class.getName(), registry);
         //classes.add(KeycloakSessionCleanupFilter.class);
 
+        context.setAttribute(ProviderSessionFactory.class.getName(), createProviderSessionFactory());
+
         TokenManager tokenManager = new TokenManager();
         SocialRequestManager socialRequestManager = new SocialRequestManager();
 
@@ -84,6 +94,15 @@ public class KeycloakApplication extends Application {
         throw new RuntimeException("Model provider not found");
     }
 
+    public static DefaultProviderSessionFactory createProviderSessionFactory() {
+        DefaultProviderSessionFactory factory = new DefaultProviderSessionFactory();
+
+        factory.registerLoader(AuditProvider.class, ProviderFactoryLoader.create(AuditProviderFactory.class), Config.getAuditProvider());
+        factory.registerLoader(AuditListener.class, ProviderFactoryLoader.create(AuditListenerFactory.class));
+
+        return factory;
+    }
+
     public KeycloakSessionFactory getFactory() {
         return factory;
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
index 4601559..cb11ed5 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -2,10 +2,14 @@ package org.keycloak.services.resources;
 
 import org.jboss.resteasy.logging.Logger;
 import org.keycloak.audit.Audit;
+import org.keycloak.audit.AuditListener;
+import org.keycloak.audit.AuditProvider;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.services.ClientConnection;
+import org.keycloak.services.ProviderSession;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.SocialRequestManager;
 import org.keycloak.services.managers.TokenManager;
@@ -19,6 +23,8 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
+import java.util.LinkedList;
+import java.util.List;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -41,7 +47,10 @@ public class RealmsResource {
     protected KeycloakSession session;
 
     @Context
-    protected HttpServletRequest servletRequest;
+    protected ProviderSession providers;
+
+    @Context
+    protected ClientConnection clientConnection;
 
     protected TokenManager tokenManager;
     protected SocialRequestManager socialRequestManager;
@@ -59,7 +68,7 @@ public class RealmsResource {
     public TokenService getTokenService(final @PathParam("realm") String name) {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = locateRealm(name, realmManager);
-        Audit audit = Audit.create(realm, servletRequest.getRemoteAddr());
+        Audit audit = createAudit(realm);
         TokenService tokenService = new TokenService(realm, tokenManager, audit);
         resourceContext.initResource(tokenService);
         return tokenService;
@@ -84,10 +93,10 @@ public class RealmsResource {
             throw new NotFoundException();
         }
 
-        Audit audit = Audit.create(realm, servletRequest.getRemoteAddr());
-
+        Audit audit = createAudit(realm);
         AccountService accountService = new AccountService(realm, application, tokenManager, socialRequestManager, audit);
         resourceContext.initResource(accountService);
+        accountService.init();
         return accountService;
     }
 
@@ -100,4 +109,24 @@ public class RealmsResource {
         return realmResource;
     }
 
+    private Audit createAudit(RealmModel realm) {
+        List<AuditListener> listeners = new LinkedList<AuditListener>();
+
+        AuditProvider auditProvider = providers.getProvider(AuditProvider.class);
+        if (auditProvider != null) {
+            listeners.add(auditProvider);
+        }
+
+        if (realm.getAuditListeners() != null) {
+            for (String id : realm.getAuditListeners()) {
+                AuditListener listener = providers.getProvider(AuditListener.class, id);
+                if (listener != null) {
+                    listeners.add(listener);
+                }
+            }
+        }
+
+        return new Audit(listeners, realm, clientConnection.getRemoteAddr());
+    }
+
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/SocialResource.java b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
index 3d5bfa5..c06fc3d 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -25,6 +25,8 @@ import org.jboss.resteasy.logging.Logger;
 import org.jboss.resteasy.spi.HttpRequest;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.audit.Audit;
+import org.keycloak.audit.AuditListener;
+import org.keycloak.audit.AuditProvider;
 import org.keycloak.audit.Details;
 import org.keycloak.audit.Errors;
 import org.keycloak.audit.Events;
@@ -36,6 +38,8 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.SocialLinkModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.services.ClientConnection;
+import org.keycloak.services.ProviderSession;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.SocialRequestManager;
@@ -67,6 +71,7 @@ import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
 import java.net.URISyntaxException;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -95,7 +100,10 @@ public class SocialResource {
     protected KeycloakSession session;
 
     @Context
-    protected HttpServletRequest servletRequest;
+    protected ProviderSession providers;
+
+    @Context
+    protected ClientConnection clientConnection;
 
     private SocialRequestManager socialRequestManager;
 
@@ -121,7 +129,7 @@ public class SocialResource {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = realmManager.getRealmByName(realmName);
 
-        Audit audit = Audit.create(realm, servletRequest.getRemoteAddr())
+        Audit audit = createAudit(realm)
                 .event(Events.LOGIN)
                 .detail(Details.RESPONSE_TYPE, "code")
                 .detail(Details.AUTH_METHOD, "social");
@@ -260,7 +268,7 @@ public class SocialResource {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = realmManager.getRealmByName(realmName);
 
-        Audit audit = Audit.create(realm, servletRequest.getRemoteAddr())
+        Audit audit = createAudit(realm)
                 .event(Events.LOGIN).client(clientId)
                 .detail(Details.REDIRECT_URI, redirectUri)
                 .detail(Details.RESPONSE_TYPE, "code")
@@ -327,4 +335,24 @@ public class SocialResource {
         return queryParams;
     }
 
+    private Audit createAudit(RealmModel realm) {
+        List<AuditListener> listeners = new LinkedList<AuditListener>();
+
+        AuditProvider auditProvider = providers.getProvider(AuditProvider.class);
+        if (auditProvider != null) {
+            listeners.add(auditProvider);
+        }
+
+        if (realm.getAuditListeners() != null) {
+            for (String id : realm.getAuditListeners()) {
+                AuditListener listener = providers.getProvider(AuditListener.class, id);
+                if (listener != null) {
+                    listeners.add(listener);
+                }
+            }
+        }
+
+        return new Audit(listeners, realm, clientConnection.getRemoteAddr());
+    }
+
 }
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 115c618..0b3cd3d 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -44,6 +44,11 @@
         </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>
diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml
index 5ca96af..56a5ed4 100755
--- a/testsuite/integration/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml
@@ -32,6 +32,24 @@
         </properties>
     </persistence-unit>
 
+    <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-unit name="picketlink-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index c2ad75e..981781f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -28,6 +28,8 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.audit.Details;
+import org.keycloak.audit.Event;
+import org.keycloak.audit.jpa.JpaAuditProviderFactory;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
@@ -38,6 +40,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
+import org.keycloak.testsuite.pages.AccountLogPage;
 import org.keycloak.testsuite.pages.AccountPasswordPage;
 import org.keycloak.testsuite.pages.AccountTotpPage;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
@@ -45,12 +48,18 @@ import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.RegisterPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
 import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -97,6 +106,9 @@ public class AccountTest {
     protected LoginPage loginPage;
 
     @WebResource
+    protected RegisterPage registerPage;
+
+    @WebResource
     protected AccountPasswordPage changePasswordPage;
 
     @WebResource
@@ -106,6 +118,9 @@ public class AccountTest {
     protected AccountTotpPage totpPage;
 
     @WebResource
+    protected AccountLogPage logPage;
+
+    @WebResource
     protected ErrorPage errorPage;
 
     private TimeBasedOTP totp = new TimeBasedOTP();
@@ -130,6 +145,8 @@ public class AccountTest {
                 appRealm.updateCredential(user, cred);
             }
         });
+
+        System.out.println(JpaAuditProviderFactory.class);
     }
 
     @Test
@@ -315,4 +332,43 @@ public class AccountTest {
         Assert.assertEquals("No access", errorPage.getError());
     }
 
+    @Test
+    public void viewLog() {
+        List<Event> e = new LinkedList<Event>();
+
+        loginPage.open();
+        loginPage.clickRegister();
+
+        registerPage.register("view", "log", "view-log@localhost", "view-log", "password", "password");
+
+        e.add(events.poll());
+        e.add(events.poll());
+
+        profilePage.open();
+        profilePage.updateProfile("view", "log2", "view-log@localhost");
+
+        e.add(events.poll());
+
+        logPage.open();
+
+        e.add(events.poll());
+
+        Collections.reverse(e);
+
+        Assert.assertTrue(logPage.isCurrent());
+
+        List<List<String>> actual = logPage.getEvents();
+
+        Assert.assertEquals(e.size(), actual.size());
+
+        Iterator<List<String>> itr = actual.iterator();
+        for (Event event : e) {
+            List<String> a = itr.next();
+            Assert.assertEquals(event.getEvent().replace('_', ' '), a.get(1));
+            Assert.assertEquals(event.getIpAddress(), a.get(2));
+            Assert.assertEquals(event.getClientId(), a.get(3));
+        }
+
+    }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 5a582e9..9f45a03 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -34,8 +34,6 @@ import java.util.concurrent.TimeUnit;
  */
 public class AssertEvents implements TestRule, AuditListenerFactory {
 
-    private static final Logger log = Logger.getLogger(AssertEvents.class);
-
     public static String DEFAULT_CLIENT_ID = "test-app";
     public static String DEFAULT_REDIRECT_URI = "http://localhost:8081/app/auth";
     public static String DEFAULT_IP_ADDRESS = "127.0.0.1";
@@ -59,6 +57,11 @@ public class AssertEvents implements TestRule, AuditListenerFactory {
     }
 
     @Override
+    public boolean lazyLoad() {
+        return false;
+    }
+
+    @Override
     public Statement apply(final Statement base, org.junit.runner.Description description) {
         return new Statement() {
             @Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountLogPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountLogPage.java
new file mode 100644
index 0000000..1f796d0
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountLogPage.java
@@ -0,0 +1,60 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.testsuite.pages;
+
+import org.keycloak.testsuite.Constants;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class AccountLogPage extends AbstractAccountPage {
+
+    private static String PATH = Constants.AUTH_SERVER_ROOT + "/rest/realms/test/account/log";
+
+    public boolean isCurrent() {
+        return driver.getTitle().contains("Account Management") && driver.getCurrentUrl().endsWith("/account/log");
+    }
+
+    public void open() {
+        driver.navigate().to(PATH);
+    }
+
+    public List<List<String>> getEvents() {
+        List<List<String>> table = new LinkedList<List<String>>();
+        for (WebElement r : driver.findElements(By.tagName("tr"))) {
+            List<String> row = new LinkedList<String>();
+            for (WebElement col : r.findElements(By.tagName("td"))) {
+                row.add(col.getText());
+            }
+            table.add(row);
+        }
+        table.remove(0);
+        return table;
+    }
+
+}