keycloak-aplcache

brute force merge

4/2/2014 9:26:33 PM

Changes

admin-ui/pom.xml 7(+4 -3)

audit/api/pom.xml 39(+39 -0)

audit/jpa/pom.xml 61(+61 -0)

audit/mongo/pom.xml 129(+129 -0)

audit/pom.xml 25(+25 -0)

audit/tests/pom.xml 35(+35 -0)

core/pom.xml 7(+4 -3)

core/src/main/java/org/keycloak/representations/idm/AuthenticationMappingRepresentation.java 39(+0 -39)

examples/pom.xml 1(+0 -1)

forms/pom.xml 3(+2 -1)

model/pom.xml 3(+1 -2)

pom.xml 14(+10 -4)

server/pom.xml 25(+22 -3)

services/pom.xml 13(+10 -3)

social/pom.xml 3(+2 -1)

spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthResult.java 44(+0 -44)

Details

admin-ui/pom.xml 7(+4 -3)

diff --git a/admin-ui/pom.xml b/admin-ui/pom.xml
index 3e98380..ddcfd21 100755
--- a/admin-ui/pom.xml
+++ b/admin-ui/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -21,8 +22,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                    <source>${maven.compiler.source}</source>
+                                    <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
index 8da77e8..d780e60 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/app.js
@@ -129,7 +129,7 @@ module.config([ '$routeProvider', function($routeProvider) {
                     return RealmLoader();
                 }
             },
-            controller : 'RealmSMTPSettingsCtrl'
+            controller : 'RealmLdapSettingsCtrl'
         })
         .when('/create/user/:realm', {
             templateUrl : 'partials/user-detail.html',
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
index 0de9f8c..b441e31 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/js/controllers/realm.js
@@ -885,4 +885,33 @@ module.controller('RealmSMTPSettingsCtrl', function($scope, Current, Realm, real
 
         return obj;
     }
+});
+
+module.controller('RealmLdapSettingsCtrl', function($scope, Realm, realm, $location, Notifications) {
+    console.log('RealmLdapSettingsCtrl');
+
+    $scope.realm = realm;
+
+    var oldCopy = angular.copy($scope.realm);
+    $scope.changed = false;
+
+    $scope.$watch('realm', function() {
+        if (!angular.equals($scope.realm, oldCopy)) {
+            $scope.changed = true;
+        }
+    }, true);
+
+    $scope.save = function() {
+        var realmCopy = angular.copy($scope.realm);
+        $scope.changed = false;
+        Realm.update(realmCopy, function () {
+            $location.url("/realms/" + realm.realm + "/ldap-settings");
+            Notifications.success("Your changes have been saved to the realm.");
+        });
+    };
+
+    $scope.reset = function() {
+        $scope.realm = angular.copy(oldCopy);
+        $scope.changed = false;
+    };
 });
\ No newline at end of file
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-ldap.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-ldap.html
index b08899f..c571eb7 100644
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-ldap.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-ldap.html
@@ -7,60 +7,39 @@
             <li><a href="#/realms/{{realm.realm}}">Settings</a></li>
             <li class="active">Ldap Configuration</li>
         </ol>
-        <h2><span>{{realm.realm}}</span> Email Server Settings</h2>
+        <h2><span>{{realm.realm}}</span> Ldap Server Settings</h2>
         <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
             <span class="fieldset-notice"><span class="required">*</span> Required fields</span>
             <fieldset>
                 <legend><span class="text">Required Settings</span></legend>
                 <div class="form-group clearfix">
-                    <label class="col-sm-2 control-label" for="smtpHost">Host <span class="required">*</span></label>
+                    <label class="col-sm-2 control-label" for="ldapConnectionUrl">Connection URL <span class="required">*</span></label>
                     <div class="col-sm-4">
-                        <input class="form-control" id="smtpHost" type="text" ng-model="realm.smtpServer.host" placeholder="SMTP Host" required>
+                        <input class="form-control" id="ldapConnectionUrl" type="text" ng-model="realm.ldapServer.connectionUrl" placeholder="LDAP connection URL" required>
                     </div>
                 </div>
                 <div class="form-group clearfix">
-                    <label class="col-sm-2 control-label" for="smtpPort">Port <span class="required">*</span></label>
+                    <label class="col-sm-2 control-label" for="ldapBaseDn">Base DN <span class="required">*</span></label>
                     <div class="col-sm-4">
-                        <input class="form-control" id="smtpPort" type="number" ng-model="realm.smtpServer.port" placeholder="SMTP Port (defaults to 25)" required>
+                        <input class="form-control" id="ldapBaseDn" type="text" ng-model="realm.ldapServer.baseDn" placeholder="LDAP Base DN" required>
                     </div>
                 </div>
                 <div class="form-group clearfix">
-                    <label class="col-sm-2 control-label" for="smtpFrom">From <span class="required">*</span></label>
+                    <label class="col-sm-2 control-label" for="ldapUserDnSuffix">User DN Suffix <span class="required">*</span></label>
                     <div class="col-sm-4">
-                        <input class="form-control" id="smtpFrom" type="email" ng-model="realm.smtpServer.from" placeholder="Sender Email Address" required>
+                        <input class="form-control" id="ldapUserDnSuffix" type="text" ng-model="realm.ldapServer.userDnSuffix" placeholder="LDAP User DN Suffix" required>
                     </div>
                 </div>
                 <div class="form-group clearfix">
-                    <label class="col-sm-2 control-label" for="smtpSSL">Enable SSL</label>
+                    <label class="col-sm-2 control-label" for="ldapBindDn">Bind DN <span class="required">*</span></label>
                     <div class="col-sm-4">
-                        <input ng-model="realm.smtpServer.ssl" name="smtpSSL" id="smtpSSL" onoffswitch />
+                        <input class="form-control" id="ldapBindDn" type="text" ng-model="realm.ldapServer.bindDn" placeholder="LDAP Bind DN" required>
                     </div>
                 </div>
                 <div class="form-group clearfix">
-                    <label class="col-sm-2 control-label" for="smtpStartTLS">Enable StartTLS</label>
+                    <label class="col-sm-2 control-label" for="ldapBindCredential">Bind Credential <span class="required">*</span></label>
                     <div class="col-sm-4">
-                        <input ng-model="realm.smtpServer.starttls" name="smtpStartTLS" id="smtpStartTLS" onoffswitch />
-                    </div>
-                </div>
-            </fieldset>
-            <fieldset>
-                <legend><span class="text">Authentication</span></legend>
-                <div class="form-group clearfix">
-                    <label class="col-sm-2 control-label" for="smtpAuth">Enable Authentication</label>
-                    <div class="col-sm-4">
-                        <input ng-model="realm.smtpServer.auth" name="smtpAuth" id="smtpAuth" onoffswitch />
-                    </div>
-                </div>
-                <div class="form-group clearfix" data-ng-show="realm.smtpServer.auth">
-                    <label class="col-sm-2 control-label" for="smtpUsername">Username <span class="required" ng-show="realm.smtpServer.auth">*</span></label>
-                    <div class="col-sm-4">
-                        <input class="form-control" id="smtpUsername" type="text" ng-model="realm.smtpServer.user" placeholder="Login Username" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
-                    </div>
-                </div>
-                <div class="form-group clearfix" data-ng-show="realm.smtpServer.auth">
-                    <label class="col-sm-2 control-label" for="smtpPassword">Password <span class="required" ng-show="realm.smtpServer.auth">*</span></label>
-                    <div class="col-sm-4">
-                        <input class="form-control" id="smtpPassword" type="password" ng-model="realm.smtpServer.password" placeholder="Login Password" ng-disabled="!realm.smtpServer.auth" ng-required="realm.smtpServer.auth">
+                        <input class="form-control" id="ldapBindCredential" type="text" ng-model="realm.ldapServer.bindCredential" placeholder="LDAP Bind Credentials" required>
                     </div>
                 </div>
             </fieldset>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-menu.html b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-menu.html
index aadf9c0..b0ae386 100755
--- a/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-menu.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/partials/realm-menu.html
@@ -1,7 +1,7 @@
 <ul data-ng-hide="createRealm">
     <li data-ng-show="access.viewRealm" data-ng-class="((!path[2] || path[1] == 'role' || path[2] == 'roles' || path[2] == 'token-settings' ||
     path[2] == 'social-settings' || path[2] == 'required-credentials' || path[2] == 'default-roles' || path[2] == 'registration-settings' ||
-    path[2] == 'keys-settings' || path[2] == 'smtp-settings') && path[3] != 'applications') && 'active'"><a href="#/realms/{{realm.realm}}">Settings</a></li>
+    path[2] == 'keys-settings' || path[2] == 'smtp-settings' || path[2] == 'ldap-settings') && path[3] != 'applications') && 'active'"><a href="#/realms/{{realm.realm}}">Settings</a></li>
     <li data-ng-show="access.viewUsers" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users">Users</a>
     </li>
     <li data-ng-show="access.viewApplications" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
diff --git a/admin-ui/src/main/resources/META-INF/resources/admin/templates/kc-navigation.html b/admin-ui/src/main/resources/META-INF/resources/admin/templates/kc-navigation.html
index 6c53b86..95dac11 100644
--- a/admin-ui/src/main/resources/META-INF/resources/admin/templates/kc-navigation.html
+++ b/admin-ui/src/main/resources/META-INF/resources/admin/templates/kc-navigation.html
@@ -7,4 +7,5 @@
     <li ng-class="{active: path[2] == 'token-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/token-settings">Token</a></li>
     <li ng-class="{active: path[2] == 'keys-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/keys-settings">Keys</a></li>
     <li ng-class="{active: path[2] == 'smtp-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/smtp-settings">Email</a></li>
+    <li ng-class="{active: path[2] == 'ldap-settings'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/ldap-settings">Ldap</a></li>
 </ul>
\ No newline at end of file
diff --git a/admin-ui-styles/pom.xml b/admin-ui-styles/pom.xml
index b6aa4c6..442de9b 100755
--- a/admin-ui-styles/pom.xml
+++ b/admin-ui-styles/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -21,8 +22,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                    <source>${maven.compiler.source}</source>
+                                    <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>

audit/api/pom.xml 39(+39 -0)

diff --git a/audit/api/pom.xml b/audit/api/pom.xml
new file mode 100755
index 0000000..facd586
--- /dev/null
+++ b/audit/api/pom.xml
@@ -0,0 +1,39 @@
+<?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-api</artifactId>
+    <name>Keycloak Audit API</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <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-model-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+   </dependencies>
+
+</project>
diff --git a/audit/api/src/main/java/org/keycloak/audit/Audit.java b/audit/api/src/main/java/org/keycloak/audit/Audit.java
new file mode 100644
index 0000000..682ceb4
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/Audit.java
@@ -0,0 +1,145 @@
+package org.keycloak.audit;
+
+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;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Audit {
+
+    private static final Logger log = Logger.getLogger(Audit.class);
+
+    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());
+            }
+        }
+
+        return new Audit(listeners, new Event()).realm(realm).ipAddress(ipAddress);
+    }
+
+    private Audit(List<AuditListener> listeners, Event event) {
+        this.listeners = listeners;
+        this.event = event;
+    }
+
+    public Audit realm(RealmModel realm) {
+        event.setRealmId(realm.getId());
+        return this;
+    }
+
+    public Audit realm(String realmId) {
+        event.setRealmId(realmId);
+        return this;
+    }
+
+    public Audit client(ClientModel client) {
+        event.setClientId(client.getClientId());
+        return this;
+    }
+
+    public Audit client(String clientId) {
+        event.setClientId(clientId);
+        return this;
+    }
+
+    public Audit user(UserModel user) {
+        event.setUserId(user.getId());
+        return this;
+    }
+
+    public Audit user(String userId) {
+        event.setUserId(userId);
+        return this;
+    }
+
+    public Audit ipAddress(String ipAddress) {
+        event.setIpAddress(ipAddress);
+        return this;
+    }
+
+    public Audit event(String e) {
+        event.setEvent(e);
+        return this;
+    }
+
+    public Audit detail(String key, String value) {
+        if (value == null || value.equals("")) {
+            return this;
+        }
+
+        if (event.getDetails() == null) {
+            event.setDetails(new HashMap<String, String>());
+        }
+        event.getDetails().put(key, value);
+        return this;
+    }
+
+    public Audit removeDetail(String key) {
+        if (event.getDetails() != null) {
+            event.getDetails().remove(key);
+        }
+        return this;
+    }
+
+    public Event getEvent() {
+        return event;
+    }
+
+    public void success() {
+        send();
+    }
+
+    public void error(String error) {
+        event.setError(error);
+        send();
+    }
+
+    public Audit clone() {
+        return new Audit(listeners, event.clone());
+    }
+
+    public Audit reset() {
+        Event old = event;
+
+        event = new Event();
+        event.setRealmId(old.getRealmId());
+        event.setIpAddress(old.getIpAddress());
+        event.setClientId(old.getClientId());
+        event.setUserId(old.getUserId());
+
+        return this;
+    }
+
+    private void send() {
+        event.setTime(System.currentTimeMillis());
+
+        if (listeners != null) {
+            for (AuditListener l : listeners) {
+                try {
+                    l.onEvent(event);
+                } catch (Throwable t) {
+                    log.error("Failed to send event to " + l, t);
+                }
+            }
+        }
+    }
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditListener.java b/audit/api/src/main/java/org/keycloak/audit/AuditListener.java
new file mode 100644
index 0000000..1425379
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditListener.java
@@ -0,0 +1,12 @@
+package org.keycloak.audit;
+
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface AuditListener extends Provider {
+
+    public void onEvent(Event event);
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java b/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java
new file mode 100644
index 0000000..6ffeb20
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditListenerFactory.java
@@ -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> {
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditProvider.java b/audit/api/src/main/java/org/keycloak/audit/AuditProvider.java
new file mode 100644
index 0000000..738b79f
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditProvider.java
@@ -0,0 +1,14 @@
+package org.keycloak.audit;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface AuditProvider extends AuditListener {
+
+    public EventQuery createQuery();
+
+    public void clear();
+
+    public void clear(long olderThan);
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java b/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java
new file mode 100644
index 0000000..8d6331e
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/AuditProviderFactory.java
@@ -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> {
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/Details.java b/audit/api/src/main/java/org/keycloak/audit/Details.java
new file mode 100644
index 0000000..1a8df0a
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/Details.java
@@ -0,0 +1,22 @@
+package org.keycloak.audit;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Details {
+
+    String EMAIL = "email";
+    String PREVIOUS_EMAIL = "previous_email";
+    String UPDATED_EMAIL = "updated_email";
+    String CODE_ID = "code_id";
+    String REDIRECT_URI = "redirect_uri";
+    String RESPONSE_TYPE = "response_type";
+    String AUTH_METHOD = "auth_method";
+    String REGISTER_METHOD = "register_method";
+    String USERNAME = "username";
+    String REMEMBER_ME = "remember_me";
+    String TOKEN_ID = "token_id";
+    String REFRESH_TOKEN_ID = "refresh_token_id";
+    String UPDATED_REFRESH_TOKEN_ID = "updated_refresh_token_id";
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/Errors.java b/audit/api/src/main/java/org/keycloak/audit/Errors.java
new file mode 100644
index 0000000..b343714
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/Errors.java
@@ -0,0 +1,36 @@
+package org.keycloak.audit;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Errors {
+
+    String REALM_DISABLED = "realm_disabled";
+
+    String CLIENT_NOT_FOUND = "client_not_found";
+    String CLIENT_DISABLED = "client_disabled";
+    String INVALID_CLIENT_CREDENTIALS = "invalid_client_credentials";
+
+    String USER_NOT_FOUND = "user_not_found";
+    String USER_DISABLED = "user_disabled";
+    String INVALID_USER_CREDENTIALS = "invalid_user_credentials";
+
+    String USERNAME_MISSING = "username_missing";
+    String USERNAME_IN_USE = "username_in_use";
+
+    String INVALID_REDIRECT_URI = "invalid_redirect_uri";
+    String INVALID_CODE = "invalid_code";
+    String INVALID_TOKEN = "invalid_token";
+    String INVALID_REGISTRATION = "invalid_registration";
+    String INVALID_FORM = "invalid_form";
+
+    String REGISTRATION_DISABLED = "registration_disabled";
+
+    String REJECTED_BY_USER = "rejected_by_user";
+
+    String NOT_ALLOWED = "not_allowed";
+
+    String SOCIAL_PROVIDER_NOT_FOUND = "social_provider_not_found";
+    String SOCIAL_ID_IN_USE = "social_id_in_use";
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/Event.java b/audit/api/src/main/java/org/keycloak/audit/Event.java
new file mode 100644
index 0000000..85b968a
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/Event.java
@@ -0,0 +1,108 @@
+package org.keycloak.audit;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Event {
+
+    private long time;
+
+    private String event;
+
+    private String realmId;
+
+    private String clientId;
+
+    private String userId;
+
+    private String ipAddress;
+
+    private String error;
+
+    private Map<String, String> details;
+
+    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 boolean isError() {
+        return error != null;
+    }
+
+    public String getError() {
+        return error;
+    }
+
+    public void setError(String error) {
+        this.error = error;
+    }
+
+    public Map<String, String> getDetails() {
+        return details;
+    }
+
+    public void setDetails(Map<String, String> details) {
+        this.details = details;
+    }
+
+    public Event clone() {
+        Event clone = new Event();
+        clone.time = time;
+        clone.event = event;
+        clone.realmId = realmId;
+        clone.clientId = clientId;
+        clone.userId = userId;
+        clone.ipAddress = ipAddress;
+        clone.error = error;
+        clone.details = details != null ? new HashMap<String, String>(details) : null;
+        return clone;
+    }
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/EventQuery.java b/audit/api/src/main/java/org/keycloak/audit/EventQuery.java
new file mode 100644
index 0000000..75e07c0
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/EventQuery.java
@@ -0,0 +1,24 @@
+package org.keycloak.audit;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface EventQuery {
+
+    public EventQuery event(String event);
+
+    public EventQuery realm(String realmId);
+
+    public EventQuery client(String clientId);
+
+    public EventQuery user(String userId);
+
+    public EventQuery firstResult(int result);
+
+    public EventQuery maxResults(int results);
+
+    public List<Event> getResultList();
+
+}
diff --git a/audit/api/src/main/java/org/keycloak/audit/Events.java b/audit/api/src/main/java/org/keycloak/audit/Events.java
new file mode 100644
index 0000000..9d1a54f
--- /dev/null
+++ b/audit/api/src/main/java/org/keycloak/audit/Events.java
@@ -0,0 +1,29 @@
+package org.keycloak.audit;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Events {
+
+    String LOGIN = "login";
+    String REGISTER = "register";
+    String LOGOUT = "logout";
+    String CODE_TO_TOKEN = "code_to_token";
+    String REFRESH_TOKEN = "refresh_token";
+
+    String SOCIAL_LINK = "social_link";
+    String REMOVE_SOCIAL_LINK = "remove_social_link";
+
+    String UPDATE_EMAIL = "update_email";
+    String UPDATE_PROFILE = "update_profile";
+    String UPDATE_PASSWORD = "update_password";
+    String UPDATE_TOTP = "update_totp";
+
+    String VERIFY_EMAIL = "verify_email";
+
+    String REMOVE_TOTP = "remove_totp";
+
+    String SEND_VERIFY_EMAIL = "send_verify_email";
+    String SEND_RESET_PASSWORD = "send_reset_password";
+
+}
diff --git a/audit/jboss-logging/pom.xml b/audit/jboss-logging/pom.xml
new file mode 100755
index 0000000..f3f11ba
--- /dev/null
+++ b/audit/jboss-logging/pom.xml
@@ -0,0 +1,39 @@
+<?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-jboss-logging</artifactId>
+    <name>Keycloak Audit JBoss Logging Provider</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <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>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+   </dependencies>
+
+</project>
diff --git a/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java
new file mode 100644
index 0000000..d5381e2
--- /dev/null
+++ b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListener.java
@@ -0,0 +1,66 @@
+package org.keycloak.audit.log;
+
+import org.jboss.logging.Logger;
+import org.keycloak.audit.AuditListener;
+import org.keycloak.audit.Event;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JBossLoggingAuditListener implements AuditListener {
+
+    private final Logger logger;
+
+    public JBossLoggingAuditListener(Logger logger) {
+        this.logger = logger;
+    }
+
+    @Override
+    public void onEvent(Event event) {
+        Logger.Level level = event.isError() ? Logger.Level.WARN : Logger.Level.INFO;
+
+        if (logger.isEnabled(level)) {
+            StringBuilder sb = new StringBuilder();
+
+            sb.append("event=");
+            sb.append(event.getEvent());
+            sb.append(", realmId=");
+            sb.append(event.getRealmId());
+            sb.append(", clientId=");
+            sb.append(event.getClientId());
+            sb.append(", userId=");
+            sb.append(event.getUserId());
+            sb.append(", ipAddress=");
+            sb.append(event.getIpAddress());
+
+            if (event.isError()) {
+                sb.append(", error=");
+                sb.append(event.getError());
+            }
+
+            if (event.getDetails() != null) {
+                for (Map.Entry<String, String> e : event.getDetails().entrySet()) {
+                    sb.append(", ");
+                    sb.append(e.getKey());
+                    if (e.getValue() == null || e.getValue().indexOf(' ') == -1) {
+                        sb.append("=");
+                        sb.append(e.getValue());
+                    } else {
+                        sb.append("='");
+                        sb.append(e.getValue());
+                        sb.append("'");
+                    }
+                }
+            }
+
+            logger.log(level, sb.toString());
+        }
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
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
new file mode 100644
index 0000000..420bd67
--- /dev/null
+++ b/audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java
@@ -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;
+    }
+
+}
diff --git a/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory b/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory
new file mode 100644
index 0000000..2370355
--- /dev/null
+++ b/audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory
@@ -0,0 +1 @@
+org.keycloak.audit.log.JBossLoggingAuditListenerFactory
\ No newline at end of file

audit/jpa/pom.xml 61(+61 -0)

diff --git a/audit/jpa/pom.xml b/audit/jpa/pom.xml
new file mode 100755
index 0000000..e8fa550
--- /dev/null
+++ b/audit/jpa/pom.xml
@@ -0,0 +1,61 @@
+<?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-jpa</artifactId>
+    <name>Keycloak Audit JPA 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>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>
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java
new file mode 100644
index 0000000..a8fd531
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/EventEntity.java
@@ -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;
+    }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java
new file mode 100644
index 0000000..0432f17
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProvider.java
@@ -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;
+    }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java
new file mode 100644
index 0000000..e05ab63
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaAuditProviderFactory.java
@@ -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;
+    }
+
+}
diff --git a/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java
new file mode 100644
index 0000000..f4ec884
--- /dev/null
+++ b/audit/jpa/src/src/main/java/org/keycloak/audit/jpa/JpaEventQuery.java
@@ -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;
+    }
+
+}
diff --git a/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory b/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
new file mode 100644
index 0000000..e04a028
--- /dev/null
+++ b/audit/jpa/src/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
@@ -0,0 +1 @@
+org.keycloak.audit.jpa.JpaAuditProviderFactory
\ No newline at end of file
diff --git a/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java b/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java
new file mode 100644
index 0000000..2cabb9c
--- /dev/null
+++ b/audit/jpa/src/src/test/java/org/keycloak/audit/jpa/JpaAuditProviderTest.java
@@ -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;
+    }
+
+}
diff --git a/audit/jpa/src/src/test/resources/META-INF/persistence.xml b/audit/jpa/src/src/test/resources/META-INF/persistence.xml
new file mode 100755
index 0000000..f537808
--- /dev/null
+++ b/audit/jpa/src/src/test/resources/META-INF/persistence.xml
@@ -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>

audit/mongo/pom.xml 129(+129 -0)

diff --git a/audit/mongo/pom.xml b/audit/mongo/pom.xml
new file mode 100755
index 0000000..53f6f73
--- /dev/null
+++ b/audit/mongo/pom.xml
@@ -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>
diff --git a/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProvider.java b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProvider.java
new file mode 100644
index 0000000..6958652
--- /dev/null
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProvider.java
@@ -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;
+    }
+
+}
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
new file mode 100644
index 0000000..5fb7a83
--- /dev/null
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoAuditProviderFactory.java
@@ -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;
+    }
+
+}
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
new file mode 100644
index 0000000..e425455
--- /dev/null
+++ b/audit/mongo/src/main/java/org/keycloak/audit/mongo/MongoEventQuery.java
@@ -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;
+    }
+
+}
diff --git a/audit/mongo/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory b/audit/mongo/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
new file mode 100644
index 0000000..ea4454d
--- /dev/null
+++ b/audit/mongo/src/main/resources/META-INF/services/org.keycloak.audit.AuditProviderFactory
@@ -0,0 +1 @@
+org.keycloak.audit.mongo.MongoAuditProviderFactory
\ No newline at end of file
diff --git a/audit/mongo/src/test/java/org/keycloak/audit/mongo/MongoAuditProviderTest.java b/audit/mongo/src/test/java/org/keycloak/audit/mongo/MongoAuditProviderTest.java
new file mode 100644
index 0000000..5ef91ad
--- /dev/null
+++ b/audit/mongo/src/test/java/org/keycloak/audit/mongo/MongoAuditProviderTest.java
@@ -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;
+    }
+
+}

audit/pom.xml 25(+25 -0)

diff --git a/audit/pom.xml b/audit/pom.xml
new file mode 100755
index 0000000..ed125d1
--- /dev/null
+++ b/audit/pom.xml
@@ -0,0 +1,25 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.0-beta-1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <name>Audit Parent</name>
+    <description/>
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.keycloak</groupId>
+    <artifactId>keycloak-audit-parent</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>api</module>
+        <module>jpa</module>
+        <module>jboss-logging</module>
+        <module>mongo</module>
+        <module>tests</module>
+    </modules>
+</project>

audit/tests/pom.xml 35(+35 -0)

diff --git a/audit/tests/pom.xml b/audit/tests/pom.xml
new file mode 100755
index 0000000..1b8cac1
--- /dev/null
+++ b/audit/tests/pom.xml
@@ -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>
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
new file mode 100644
index 0000000..c5d1bb7
--- /dev/null
+++ b/audit/tests/src/main/java/org/keycloak/audit/tests/AbstractAuditProviderTest.java
@@ -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;
+    }
+
+}

core/pom.xml 7(+4 -3)

diff --git a/core/pom.xml b/core/pom.xml
index 1a5363b..2d4b51f 100755
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -45,8 +46,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/core/src/main/java/org/keycloak/provider/Provider.java b/core/src/main/java/org/keycloak/provider/Provider.java
new file mode 100644
index 0000000..3401266
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/Provider.java
@@ -0,0 +1,10 @@
+package org.keycloak.provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Provider {
+
+    public void close();
+
+}
diff --git a/core/src/main/java/org/keycloak/provider/ProviderFactory.java b/core/src/main/java/org/keycloak/provider/ProviderFactory.java
new file mode 100644
index 0000000..720538d
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/ProviderFactory.java
@@ -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();
+
+}
diff --git a/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java b/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
new file mode 100644
index 0000000..51b00dc
--- /dev/null
+++ b/core/src/main/java/org/keycloak/provider/ProviderFactoryLoader.java
@@ -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;
+                    }
+                }
+            }
+        }
+
+    }
+
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index fc72ef0..a4278d2 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -39,7 +39,6 @@ public class RealmRepresentation {
     protected Map<String, List<UserRoleMappingRepresentation>> applicationRoleMappings;
     protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
     protected List<SocialMappingRepresentation> socialMappings;
-    protected List<AuthenticationMappingRepresentation> authenticationMappings;
     protected List<ApplicationRepresentation> applications;
     protected List<OAuthClientRepresentation> oauthClients;
     protected Map<String, String> socialProviders;
@@ -181,18 +180,6 @@ public class RealmRepresentation {
         return mapping;
     }
 
-    public List<AuthenticationMappingRepresentation> getAuthenticationMappings() {
-        return authenticationMappings;
-    }
-
-    public AuthenticationMappingRepresentation authenticationMapping(String username) {
-        AuthenticationMappingRepresentation mapping = new AuthenticationMappingRepresentation();
-        mapping.setUsername(username);
-        if (authenticationMappings == null) authenticationMappings = new ArrayList<AuthenticationMappingRepresentation>();
-        authenticationMappings.add(mapping);
-        return mapping;
-    }
-
     public Set<String> getRequiredCredentials() {
         return requiredCredentials;
     }
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index 5fc55f0..43aa368 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -20,6 +20,7 @@ public class UserRepresentation {
     protected String firstName;
     protected String lastName;
     protected String email;
+    protected AuthenticationLinkRepresentation authenticationLink;
     protected Map<String, String> attributes;
     protected List<CredentialRepresentation> credentials;
     protected List<String> requiredActions;
@@ -96,6 +97,14 @@ public class UserRepresentation {
         this.emailVerified = emailVerified;
     }
 
+    public AuthenticationLinkRepresentation getAuthenticationLink() {
+        return authenticationLink;
+    }
+
+    public void setAuthenticationLink(AuthenticationLinkRepresentation authenticationLink) {
+        this.authenticationLink = authenticationLink;
+    }
+
     public Map<String, String> getAttributes() {
         return attributes;
     }
diff --git a/core/src/main/java/org/keycloak/util/KeycloakUriBuilder.java b/core/src/main/java/org/keycloak/util/KeycloakUriBuilder.java
index 111c4a4..1e05af4 100755
--- a/core/src/main/java/org/keycloak/util/KeycloakUriBuilder.java
+++ b/core/src/main/java/org/keycloak/util/KeycloakUriBuilder.java
@@ -620,8 +620,6 @@ public class KeycloakUriBuilder {
         }
         // don't set values if values is null
         if (values == null) return this;
-        // don't set values if values is null
-        if (values == null) return this;
         return queryParam(name, values);
     }
 
diff --git a/core-jaxrs/pom.xml b/core-jaxrs/pom.xml
index 7174d6f..2ca0392 100755
--- a/core-jaxrs/pom.xml
+++ b/core-jaxrs/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -61,8 +62,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/distribution/appliance-dist/pom.xml b/distribution/appliance-dist/pom.xml
index 1b20c25..c1a7bb4 100755
--- a/distribution/appliance-dist/pom.xml
+++ b/distribution/appliance-dist/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/distribution/as7-adapter-zip/pom.xml b/distribution/as7-adapter-zip/pom.xml
index ae97075..d2379ab 100755
--- a/distribution/as7-adapter-zip/pom.xml
+++ b/distribution/as7-adapter-zip/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/distribution/eap6-adapter-zip/pom.xml b/distribution/eap6-adapter-zip/pom.xml
index 1d91b18..153a450 100755
--- a/distribution/eap6-adapter-zip/pom.xml
+++ b/distribution/eap6-adapter-zip/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/distribution/examples-docs-zip/pom.xml b/distribution/examples-docs-zip/pom.xml
index d1e953f..ee31f51 100755
--- a/distribution/examples-docs-zip/pom.xml
+++ b/distribution/examples-docs-zip/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/distribution/pom.xml b/distribution/pom.xml
index ed51ec9..3da5520 100755
--- a/distribution/pom.xml
+++ b/distribution/pom.xml
@@ -10,7 +10,6 @@
     <description/>
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>org.keycloak</groupId>
     <artifactId>distribution-pom</artifactId>
     <packaging>pom</packaging>
 
diff --git a/distribution/src-dist/pom.xml b/distribution/src-dist/pom.xml
index c31c69d..95d7c12 100755
--- a/distribution/src-dist/pom.xml
+++ b/distribution/src-dist/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/distribution/theme-template-zip/pom.xml b/distribution/theme-template-zip/pom.xml
index 86c1cd3..5742cc5 100755
--- a/distribution/theme-template-zip/pom.xml
+++ b/distribution/theme-template-zip/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/distribution/war-dist/pom.xml b/distribution/war-dist/pom.xml
index e4950e4..19a0980 100755
--- a/distribution/war-dist/pom.xml
+++ b/distribution/war-dist/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/distribution/war-zip/pom.xml b/distribution/war-zip/pom.xml
index 599f4c8..3c75d9c 100755
--- a/distribution/war-zip/pom.xml
+++ b/distribution/war-zip/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/distribution/wildfly-adapter-zip/pom.xml b/distribution/wildfly-adapter-zip/pom.xml
index 7e9ae0b..9c55d04 100755
--- a/distribution/wildfly-adapter-zip/pom.xml
+++ b/distribution/wildfly-adapter-zip/pom.xml
@@ -1,4 +1,5 @@
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <artifactId>keycloak-parent</artifactId>
diff --git a/examples/demo-template/customer-app/pom.xml b/examples/demo-template/customer-app/pom.xml
index 34cfa9f..f35553b 100755
--- a/examples/demo-template/customer-app/pom.xml
+++ b/examples/demo-template/customer-app/pom.xml
@@ -67,8 +67,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/examples/demo-template/customer-app-cli/pom.xml b/examples/demo-template/customer-app-cli/pom.xml
index 363bc3e..ac8ffb3 100755
--- a/examples/demo-template/customer-app-cli/pom.xml
+++ b/examples/demo-template/customer-app-cli/pom.xml
@@ -43,8 +43,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
             <plugin>
diff --git a/examples/demo-template/customer-app-js/pom.xml b/examples/demo-template/customer-app-js/pom.xml
index 0b75bb3..d230c41 100755
--- a/examples/demo-template/customer-app-js/pom.xml
+++ b/examples/demo-template/customer-app-js/pom.xml
@@ -33,8 +33,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/examples/demo-template/database-service/pom.xml b/examples/demo-template/database-service/pom.xml
index ebf0afc..f85e1c5 100755
--- a/examples/demo-template/database-service/pom.xml
+++ b/examples/demo-template/database-service/pom.xml
@@ -78,8 +78,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/examples/demo-template/pom.xml b/examples/demo-template/pom.xml
index 2c98df4..8736c88 100755
--- a/examples/demo-template/pom.xml
+++ b/examples/demo-template/pom.xml
@@ -10,7 +10,6 @@
     <description/>
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>org.keycloak</groupId>
     <artifactId>demo-pom</artifactId>
     <packaging>pom</packaging>
 
diff --git a/examples/demo-template/product-app/pom.xml b/examples/demo-template/product-app/pom.xml
index f5c5ca4..0b4b6f4 100755
--- a/examples/demo-template/product-app/pom.xml
+++ b/examples/demo-template/product-app/pom.xml
@@ -72,8 +72,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/examples/demo-template/third-party/pom.xml b/examples/demo-template/third-party/pom.xml
index 5142f7b..0b2b478 100755
--- a/examples/demo-template/third-party/pom.xml
+++ b/examples/demo-template/third-party/pom.xml
@@ -65,8 +65,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/examples/demo-template/third-party-cdi/pom.xml b/examples/demo-template/third-party-cdi/pom.xml
index e98f7fe..ab1d5ad 100755
--- a/examples/demo-template/third-party-cdi/pom.xml
+++ b/examples/demo-template/third-party-cdi/pom.xml
@@ -83,8 +83,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/examples/js-console/pom.xml b/examples/js-console/pom.xml
index 1e8dc6a..9c1ca74 100755
--- a/examples/js-console/pom.xml
+++ b/examples/js-console/pom.xml
@@ -33,8 +33,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>

examples/pom.xml 1(+0 -1)

diff --git a/examples/pom.xml b/examples/pom.xml
index 7c5c144..15fd6e2 100755
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -10,7 +10,6 @@
     <description/>
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>org.keycloak</groupId>
     <artifactId>examples-pom</artifactId>
     <packaging>pom</packaging>
 
diff --git a/forms/account-api/pom.xml b/forms/account-api/pom.xml
index c200575..e153d52 100755
--- a/forms/account-api/pom.xml
+++ b/forms/account-api/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-forms</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -39,8 +40,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                    <source>${maven.compiler.source}</source>
+                                    <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>
diff --git a/forms/account-freemarker/pom.xml b/forms/account-freemarker/pom.xml
index 26b72c2..c438fed 100755
--- a/forms/account-freemarker/pom.xml
+++ b/forms/account-freemarker/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-forms</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -60,8 +61,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                    <source>${maven.compiler.source}</source>
+                                    <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>
diff --git a/forms/common-freemarker/pom.xml b/forms/common-freemarker/pom.xml
index 64f4043..eaac9f3 100755
--- a/forms/common-freemarker/pom.xml
+++ b/forms/common-freemarker/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-forms</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -44,8 +45,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                    <source>${maven.compiler.source}</source>
+                                    <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>
diff --git a/forms/common-themes/pom.xml b/forms/common-themes/pom.xml
index 338fdf2..cb1d6f9 100755
--- a/forms/common-themes/pom.xml
+++ b/forms/common-themes/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-forms</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -44,8 +45,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                    <source>${maven.compiler.source}</source>
+                                    <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>
diff --git a/forms/login-api/pom.xml b/forms/login-api/pom.xml
index 6ba35dc..9b44836 100755
--- a/forms/login-api/pom.xml
+++ b/forms/login-api/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-forms</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -39,8 +40,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                    <source>${maven.compiler.source}</source>
+                                    <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>
diff --git a/forms/login-freemarker/pom.xml b/forms/login-freemarker/pom.xml
index 763ae02..d1cc829 100755
--- a/forms/login-freemarker/pom.xml
+++ b/forms/login-freemarker/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-forms</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -65,8 +66,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                        <source>${maven.compiler.source}</source>
+                                        <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>

forms/pom.xml 3(+2 -1)

diff --git a/forms/pom.xml b/forms/pom.xml
index b658295..f70ac61 100755
--- a/forms/pom.xml
+++ b/forms/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
diff --git a/integration/adapter-core/pom.xml b/integration/adapter-core/pom.xml
index f0d9ec0..9a57dba 100755
--- a/integration/adapter-core/pom.xml
+++ b/integration/adapter-core/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -58,8 +59,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/integration/as7-eap6/adapter/pom.xml b/integration/as7-eap6/adapter/pom.xml
index 30b844c..eaa83de 100755
--- a/integration/as7-eap6/adapter/pom.xml
+++ b/integration/as7-eap6/adapter/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -88,8 +89,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/integration/as7-eap-subsystem/pom.xml b/integration/as7-eap-subsystem/pom.xml
index 43a79e6..8c8f472 100755
--- a/integration/as7-eap-subsystem/pom.xml
+++ b/integration/as7-eap-subsystem/pom.xml
@@ -24,22 +24,18 @@
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
-    <groupId>org.keycloak</groupId>
     <artifactId>keycloak-as7-subsystem</artifactId>
-    <version>1.0-beta-1-SNAPSHOT</version>
-
     <name>Keycloak Wildfly Subsystem</name>
-
     <packaging>jar</packaging>
 
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>2.3.1</version>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
             <plugin>
diff --git a/integration/installed/pom.xml b/integration/installed/pom.xml
index 78860ce..09f3e1a 100755
--- a/integration/installed/pom.xml
+++ b/integration/installed/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -52,8 +53,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/integration/jaxrs-oauth-client/pom.xml b/integration/jaxrs-oauth-client/pom.xml
index 70cda06..f4243d8 100755
--- a/integration/jaxrs-oauth-client/pom.xml
+++ b/integration/jaxrs-oauth-client/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -61,8 +62,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/integration/jboss-adapter-core/pom.xml b/integration/jboss-adapter-core/pom.xml
index b37011f..42ec336 100755
--- a/integration/jboss-adapter-core/pom.xml
+++ b/integration/jboss-adapter-core/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -73,8 +74,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/integration/js/pom.xml b/integration/js/pom.xml
index 335f432..fdfc8a3 100755
--- a/integration/js/pom.xml
+++ b/integration/js/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>
@@ -21,8 +22,8 @@
 				<groupId>org.apache.maven.plugins</groupId>
 				<artifactId>maven-compiler-plugin</artifactId>
 				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
+                                    <source>${maven.compiler.source}</source>
+                                    <target>${maven.compiler.target}</target>
 				</configuration>
 			</plugin>
 		</plugins>
diff --git a/integration/pom.xml b/integration/pom.xml
index 5904823..366904c 100755
--- a/integration/pom.xml
+++ b/integration/pom.xml
@@ -10,7 +10,6 @@
     <description/>
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>org.keycloak</groupId>
     <artifactId>integration-pom</artifactId>
     <packaging>pom</packaging>
 
diff --git a/integration/servlet-oauth-client/pom.xml b/integration/servlet-oauth-client/pom.xml
index 9611ccc..68b0f81 100755
--- a/integration/servlet-oauth-client/pom.xml
+++ b/integration/servlet-oauth-client/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -58,8 +59,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/integration/undertow/pom.xml b/integration/undertow/pom.xml
index fe233f6..44c6a0a 100755
--- a/integration/undertow/pom.xml
+++ b/integration/undertow/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -82,8 +83,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/integration/wildfly-adapter/pom.xml b/integration/wildfly-adapter/pom.xml
index a39d1b8..e563cef 100755
--- a/integration/wildfly-adapter/pom.xml
+++ b/integration/wildfly-adapter/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -98,8 +99,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/integration/wildfly-subsystem/pom.xml b/integration/wildfly-subsystem/pom.xml
index da6f73a..7c006b6 100755
--- a/integration/wildfly-subsystem/pom.xml
+++ b/integration/wildfly-subsystem/pom.xml
@@ -24,22 +24,18 @@
         <relativePath>../../pom.xml</relativePath>
     </parent>
 
-    <groupId>org.keycloak</groupId>
     <artifactId>keycloak-wildfly-subsystem</artifactId>
-    <version>1.0-beta-1-SNAPSHOT</version>
-
     <name>Keycloak Wildfly Subsystem</name>
-
     <packaging>jar</packaging>
 
     <build>
         <plugins>
             <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>2.3.1</version>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
             <plugin>
diff --git a/model/api/pom.xml b/model/api/pom.xml
index 6052c29..fbcfd53 100755
--- a/model/api/pom.xml
+++ b/model/api/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -40,8 +41,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java
index 6a0f1c6..7dea3c9 100644
--- a/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticationProviderModel.java
@@ -1,5 +1,6 @@
 package org.keycloak.models;
 
+import java.util.Collections;
 import java.util.Map;
 
 /**
@@ -7,6 +8,8 @@ import java.util.Map;
  */
 public class AuthenticationProviderModel {
 
+    public static final AuthenticationProviderModel DEFAULT_PROVIDER = new AuthenticationProviderModel("model", true, Collections.EMPTY_MAP);
+
     private String providerName;
     private boolean passwordUpdateSupported = true;
     private Map<String, String> config;
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 308dd7c..db271f2 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -138,11 +138,9 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
 
     boolean removeSocialLink(UserModel user, String socialProvider);
 
-    UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink);
+    AuthenticationLinkModel getAuthenticationLink(UserModel user);
 
-    Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user);
-
-    void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink);
+    void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink);
 
     boolean isSocial();
 
@@ -209,4 +207,8 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
     void setNotBefore(int notBefore);
 
     boolean removeRoleById(String id);
+
+    Set<String> getAuditListeners();
+
+    void setAuditListeners(Set<String> listeners);
 }
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 617b8f4..0fec0af 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -86,8 +87,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
index 0eecfba..e6ae8d4 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java
@@ -6,16 +6,13 @@ import javax.persistence.Id;
 import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
+import javax.persistence.OneToOne;
 
 import org.hibernate.annotations.GenericGenerator;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
-@NamedQueries({
-        @NamedQuery(name="findAuthLinkByUser", query="select link from AuthenticationLinkEntity link where link.user = :user"),
-        @NamedQuery(name="findUserByAuthLinkAndRealm", query="select link.user from AuthenticationLinkEntity link where link.realm = :realm and link.authProvider = :authProvider and link.authUserId = :authUserId")
-})
 @Entity
 public class AuthenticationLinkEntity {
 
@@ -24,12 +21,6 @@ public class AuthenticationLinkEntity {
     @GeneratedValue(generator = "keycloak_generator")
     private String id;
 
-    @ManyToOne
-    private UserEntity user;
-
-    @ManyToOne
-    protected RealmEntity realm;
-
     protected String authProvider;
     protected String authUserId;
 
@@ -41,22 +32,6 @@ public class AuthenticationLinkEntity {
         this.id = id;
     }
 
-    public UserEntity getUser() {
-        return user;
-    }
-
-    public void setUser(UserEntity user) {
-        this.user = user;
-    }
-
-    public RealmEntity getRealm() {
-        return realm;
-    }
-
-    public void setRealm(RealmEntity realm) {
-        this.realm = realm;
-    }
-
     public String getAuthProvider() {
         return authProvider;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java
index 95e113d..8517ad2 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java
@@ -25,6 +25,7 @@ public class AuthenticationProviderEntity {
 
     private String providerName;
     private boolean passwordUpdateSupported;
+    private int priority;
 
     @ElementCollection
     @MapKeyColumn(name="name")
@@ -56,6 +57,14 @@ public class AuthenticationProviderEntity {
         this.passwordUpdateSupported = passwordUpdateSupported;
     }
 
+    public int getPriority() {
+        return priority;
+    }
+
+    public void setPriority(int priority) {
+        this.priority = priority;
+    }
+
     public Map<String, String> getConfig() {
         return config;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 255b737..1e3a9d7 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -16,7 +16,10 @@ import javax.persistence.OneToMany;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -66,7 +69,7 @@ public class RealmEntity {
 
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
     @JoinTable(name="AuthProviders")
-    Collection<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
+    List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
 
     @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
@@ -96,6 +99,9 @@ public class RealmEntity {
     @JoinTable(name="RealmDefaultRoles")
     Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
 
+    @ElementCollection
+    protected Set<String> auditListeners= new HashSet<String>();
+
     public String getId() {
         return id;
     }
@@ -240,11 +246,11 @@ public class RealmEntity {
         this.requiredCredentials = requiredCredentials;
     }
 
-    public Collection<AuthenticationProviderEntity> getAuthenticationProviders() {
+    public List<AuthenticationProviderEntity> getAuthenticationProviders() {
         return authenticationProviders;
     }
 
-    public void setAuthenticationProviders(Collection<AuthenticationProviderEntity> authenticationProviders) {
+    public void setAuthenticationProviders(List<AuthenticationProviderEntity> authenticationProviders) {
         this.authenticationProviders = authenticationProviders;
     }
 
@@ -342,5 +348,13 @@ public class RealmEntity {
     public void setBruteForceProtected(boolean bruteForceProtected) {
         this.bruteForceProtected = bruteForceProtected;
     }
+
+    public Set<String> getAuditListeners() {
+        return auditListeners;
+    }
+
+    public void setAuditListeners(Set<String> auditListeners) {
+        this.auditListeners = auditListeners;
+    }
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index 156aaaa..1ffba3b 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -15,6 +15,8 @@ import javax.persistence.MapKeyColumn;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
 import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -65,6 +67,9 @@ public class UserEntity {
     @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
     protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
 
+    @OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
+    protected AuthenticationLinkEntity authenticationLink;
+
     public String getId() {
         return id;
     }
@@ -161,6 +166,14 @@ public class UserEntity {
         this.credentials = credentials;
     }
 
+    public AuthenticationLinkEntity getAuthenticationLink() {
+        return authenticationLink;
+    }
+
+    public void setAuthenticationLink(AuthenticationLinkEntity authenticationLink) {
+        this.authenticationLink = authenticationLink;
+    }
+
     public int getNotBefore() {
         return notBefore;
     }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 0513650..0275f40 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -40,6 +40,8 @@ import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -429,7 +431,9 @@ public class RealmAdapter implements RealmModel {
     private void removeUser(UserEntity user) {
         em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
         em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
-        em.createQuery("delete from " + AuthenticationLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
+        if (user.getAuthenticationLink() != null) {
+            em.remove(user.getAuthenticationLink());
+        }
         em.remove(user);
     }
 
@@ -648,43 +652,22 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
-    public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
-        TypedQuery<UserEntity> query = em.createNamedQuery("findUserByAuthLinkAndRealm", UserEntity.class);
-        query.setParameter("realm", realm);
-        query.setParameter("authProvider", authenticationLink.getAuthProvider());
-        query.setParameter("authUserId", authenticationLink.getAuthUserId());
-        List<UserEntity> results = query.getResultList();
-        if (results.isEmpty()) {
-            return null;
-        } else if (results.size() > 1) {
-            throw new IllegalStateException("More results found for authenticationProvider=" + authenticationLink.getAuthProvider() +
-                    ", authUserId=" + authenticationLink.getAuthUserId() + ", results=" + results);
-        } else {
-            UserEntity user = results.get(0);
-            return new UserAdapter(user);
-        }
-    }
-
-    @Override
-    public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
-        TypedQuery<AuthenticationLinkEntity> query = em.createNamedQuery("findAuthLinkByUser", AuthenticationLinkEntity.class);
-        query.setParameter("user", ((UserAdapter) user).getUser());
-        List<AuthenticationLinkEntity> results = query.getResultList();
-        Set<AuthenticationLinkModel> set = new HashSet<AuthenticationLinkModel>();
-        for (AuthenticationLinkEntity entity : results) {
-            set.add(new AuthenticationLinkModel(entity.getAuthProvider(), entity.getAuthUserId()));
-        }
-        return set;
+    public AuthenticationLinkModel getAuthenticationLink(UserModel user) {
+        UserEntity userEntity = ((UserAdapter) user).getUser();
+        AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
+        return authLinkEntity == null ? null : new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
     }
 
     @Override
-    public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
+    public void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
         AuthenticationLinkEntity entity = new AuthenticationLinkEntity();
-        entity.setRealm(realm);
         entity.setAuthProvider(authenticationLink.getAuthProvider());
         entity.setAuthUserId(authenticationLink.getAuthUserId());
-        entity.setUser(((UserAdapter) user).getUser());
+
+        UserEntity userEntity = ((UserAdapter) user).getUser();
+        userEntity.setAuthenticationLink(entity);
         em.persist(entity);
+        em.persist(userEntity);
         em.flush();
     }
 
@@ -850,7 +833,15 @@ public class RealmAdapter implements RealmModel {
 
     @Override
     public List<AuthenticationProviderModel> getAuthenticationProviders() {
-        Collection<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
+        List<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
+        Collections.sort(entities, new Comparator<AuthenticationProviderEntity>() {
+
+            @Override
+            public int compare(AuthenticationProviderEntity o1, AuthenticationProviderEntity o2) {
+                return o1.getPriority() - o2.getPriority();
+            }
+
+        });
         List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
         for (AuthenticationProviderEntity entity : entities) {
             result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
@@ -862,11 +853,13 @@ public class RealmAdapter implements RealmModel {
     @Override
     public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
         List<AuthenticationProviderEntity> newEntities = new ArrayList<AuthenticationProviderEntity>();
+        int counter = 1;
         for (AuthenticationProviderModel model : authenticationProviders) {
             AuthenticationProviderEntity entity = new AuthenticationProviderEntity();
             entity.setProviderName(model.getProviderName());
             entity.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
             entity.setConfig(model.getConfig());
+            entity.setPriority(counter++);
             newEntities.add(entity);
         }
 
@@ -1190,4 +1183,15 @@ public class RealmAdapter implements RealmModel {
         realm.setAccountTheme(name);
         em.flush();
     }
+
+    @Override
+    public Set<String> getAuditListeners() {
+        return realm.getAuditListeners();
+    }
+
+    @Override
+    public void setAuditListeners(Set<String> listeners) {
+        realm.setAuditListeners(listeners);
+        em.flush();
+    }
 }
diff --git a/model/mongo/pom.xml b/model/mongo/pom.xml
index 1436419..53a4687 100755
--- a/model/mongo/pom.xml
+++ b/model/mongo/pom.xml
@@ -75,8 +75,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 6f07885..8d3943b 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -39,6 +39,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -968,41 +969,26 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
     }
 
     @Override
-    public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
-        DBObject query = new QueryBuilder()
-                .and("authenticationLinks.authProvider").is(authenticationLink.getAuthProvider())
-                .and("authenticationLinks.authUserId").is(authenticationLink.getAuthUserId())
-                .and("realmId").is(getId())
-                .get();
-        UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
-        return userEntity==null ? null : new UserAdapter(userEntity, invocationContext);
-    }
-
-    @Override
-    public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
+    public AuthenticationLinkModel getAuthenticationLink(UserModel user) {
         UserEntity userEntity = ((UserAdapter)user).getUser();
-        List<AuthenticationLinkEntity> linkEntities = userEntity.getAuthenticationLinks();
-
-        if (linkEntities == null) {
-            return Collections.EMPTY_SET;
-        }
+        AuthenticationLinkEntity authLinkEntity = userEntity.getAuthenticationLink();
 
-        Set<AuthenticationLinkModel> result = new HashSet<AuthenticationLinkModel>();
-        for (AuthenticationLinkEntity authLinkEntity : linkEntities) {
-            AuthenticationLinkModel model = new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
-            result.add(model);
+        if (authLinkEntity == null) {
+            return null;
+        }  else {
+            return new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
         }
-        return result;
     }
 
     @Override
-    public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
+    public void setAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
         UserEntity userEntity = ((UserAdapter)user).getUser();
         AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity();
         authLinkEntity.setAuthProvider(authenticationLink.getAuthProvider());
         authLinkEntity.setAuthUserId(authenticationLink.getAuthUserId());
+        userEntity.setAuthenticationLink(authLinkEntity);
 
-        getMongoStore().pushItemToList(userEntity, "authenticationLinks", authLinkEntity, true, invocationContext);
+        getMongoStore().updateEntity(userEntity, invocationContext);
     }
 
     protected void updateRealm() {
@@ -1156,6 +1142,20 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
     }
 
     @Override
+    public Set<String> getAuditListeners() {
+        return realm.getAuditListeners() != null ? new HashSet<String>(realm.getAuditListeners()) : null;
+    }
+
+    @Override
+    public void setAuditListeners(Set<String> listeners) {
+         if (listeners != null) {
+             realm.setAuditListeners(new LinkedList<String>(listeners));
+         } else {
+             realm.setAuditListeners(null);
+         }
+    }
+
+    @Override
     public RealmEntity getMongoEntity() {
         return realm;
     }
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
index 991bd07..58b9ba2 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/RealmEntity.java
@@ -10,8 +10,11 @@ import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -54,6 +57,8 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
     private Map<String, String> socialConfig = new HashMap<String, String>();
     private Map<String, String> ldapServerConfig;
 
+    private List<String> auditListeners = new LinkedList<String>();
+
     @MongoField
     public String getName() {
         return name;
@@ -297,6 +302,15 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
         this.ldapServerConfig = ldapServerConfig;
     }
 
+    @MongoField
+    public List<String> getAuditListeners() {
+        return auditListeners;
+    }
+
+    public void setAuditListeners(List<String> auditListeners) {
+        this.auditListeners = auditListeners;
+    }
+
     @Override
     public void afterRemove(MongoStoreInvocationContext context) {
         DBObject query = new QueryBuilder()
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
index 4468fd9..40aeae5 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserEntity.java
@@ -38,7 +38,7 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
     private List<UserModel.RequiredAction> requiredActions;
     private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
     private List<SocialLinkEntity> socialLinks;
-    private List<AuthenticationLinkEntity> authenticationLinks;
+    private AuthenticationLinkEntity authenticationLink;
 
     @MongoField
     public String getLoginName() {
@@ -168,12 +168,12 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
     }
 
     @MongoField
-    public List<AuthenticationLinkEntity> getAuthenticationLinks() {
-        return authenticationLinks;
+    public AuthenticationLinkEntity getAuthenticationLink() {
+        return authenticationLink;
     }
 
-    public void setAuthenticationLinks(List<AuthenticationLinkEntity> authenticationLinks) {
-        this.authenticationLinks = authenticationLinks;
+    public void setAuthenticationLink(AuthenticationLinkEntity authenticationLink) {
+        this.authenticationLink = authenticationLink;
     }
 
     @MongoField
diff --git a/model/picketlink/pom.xml b/model/picketlink/pom.xml
index ce62835..d2250f8 100755
--- a/model/picketlink/pom.xml
+++ b/model/picketlink/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -66,8 +67,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>

model/pom.xml 3(+1 -2)

diff --git a/model/pom.xml b/model/pom.xml
index ee87987..7a3ada1 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -6,11 +6,10 @@
         <version>1.0-beta-1-SNAPSHOT</version>
         <relativePath>../pom.xml</relativePath>
     </parent>
-    <name>Examples</name>
+    <name>Model Parent</name>
     <description/>
     <modelVersion>4.0.0</modelVersion>
 
-    <groupId>org.keycloak</groupId>
     <artifactId>keycloak-model-pom</artifactId>
     <packaging>pom</packaging>
 
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
index 1abcf5f..6c1cece 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthenticationManagerTest.java
@@ -3,6 +3,7 @@ package org.keycloak.model.test;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
@@ -14,6 +15,8 @@ import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus
 
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
+
+import java.util.Arrays;
 import java.util.UUID;
 
 public class AuthenticationManagerTest extends AbstractModelTest {
@@ -138,6 +141,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
         realm.setPublicKeyPem("0234234");
         realm.setAccessTokenLifespan(1000);
         realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
+        realm.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
 
         am = new AuthenticationManager();
 
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
index c0c531c..05374a5 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersExternalModelTest.java
@@ -49,6 +49,8 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
         realm2 = realmManager.createRealm("realm2");
         realm1.addRequiredCredential(CredentialRepresentation.PASSWORD);
         realm2.addRequiredCredential(CredentialRepresentation.PASSWORD);
+        realm1.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
+        realm2.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
 
         UserModel john = realm1.addUser("john");
         john.setEnabled(true);
@@ -93,10 +95,10 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
             Assert.assertEquals("john@email.org", john2.getEmail());
 
             // Verify link exists
-            Set<AuthenticationLinkModel> authLinks = realm2.getAuthenticationLinks(john2);
-            Assert.assertEquals(1, authLinks.size());
-            AuthenticationLinkModel authLink = authLinks.iterator().next();
+            AuthenticationLinkModel authLink = realm2.getAuthenticationLink(john2);
+            Assert.assertNotNull(authLink);
             Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL);
+            Assert.assertEquals(authLink.getAuthUserId(), realm1.getUser("john").getId());
         } finally {
             ResteasyProviderFactory.clearContextData();
         }
@@ -108,14 +110,19 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
         // Add externalModel authenticationProvider into realm2 and point to realm1
         setupAuthenticationProviders();
 
+        // Add john to realm2 and set authentication link
+        UserModel john = realm2.addUser("john");
+        john.setEnabled(true);
+        realm2.setAuthenticationLink(john, new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, realm1.getUser("john").getId()));
+
         try {
             // this is needed for externalModel provider
             ResteasyProviderFactory.pushContext(KeycloakSession.class, identitySession);
 
-            // Change credential via realm2 and validate that they are changed in both realms
+            // Change credential via realm2 and validate that they are changed also in realm1
             AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm2);
             try {
-                authProviderManager.updatePassword("john", "password-updated");
+                Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated"));
             } catch (AuthenticationProviderException ape) {
                 ape.printStackTrace();
                 Assert.fail("Error not expected");
@@ -130,14 +137,14 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
 
             // Change credential and validate that password is updated just for realm2
             try {
-                authProviderManager.updatePassword("john", "password-updated2");
+                Assert.assertFalse(authProviderManager.updatePassword(john, "password-updated2"));
             } catch (AuthenticationProviderException ape) {
                 ape.printStackTrace();
                 Assert.fail("Error not expected");
             }
             formData = createFormData("john", "password-updated2");
             Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(null, realm1, formData));
-            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm2, formData));
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(null, realm2, formData));
 
 
             // Allow passwordUpdate propagation again
@@ -146,7 +153,7 @@ public class AuthProvidersExternalModelTest extends AbstractModelTest {
             // Set passwordPolicy for realm1 and verify that password update fail
             realm1.setPasswordPolicy(new PasswordPolicy("length(8)"));
             try {
-                authProviderManager.updatePassword("john", "passw");
+                authProviderManager.updatePassword(john, "passw");
                 Assert.fail("Update not expected to pass");
             } catch (AuthenticationProviderException ape) {
 
diff --git a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
index 07526c2..91f99dd 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/AuthProvidersLDAPTest.java
@@ -95,9 +95,8 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
             Assert.assertEquals("john@email.org", john.getEmail());
 
             // Verify link exists
-            Set<AuthenticationLinkModel> authLinks = realm.getAuthenticationLinks(john);
-            Assert.assertEquals(1, authLinks.size());
-            AuthenticationLinkModel authLink = authLinks.iterator().next();
+            AuthenticationLinkModel authLink = realm.getAuthenticationLink(john);
+            Assert.assertNotNull(authLink);
             Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_PICKETLINK);
         } finally {
             ResteasyProviderFactory.clearContextData();
@@ -114,6 +113,7 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
 
             // Add some user and password to realm
             UserModel realmUser = realm.addUser("realmUser");
+            realmUser.setEnabled(true);
             UserCredentialModel credential = new UserCredentialModel();
             credential.setType(CredentialRepresentation.PASSWORD);
             credential.setValue("pass");
@@ -135,6 +135,11 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
             realmUser.setEnabled(false);
             formData = AuthProvidersExternalModelTest.createFormData("realmUser", "pass");
             Assert.assertEquals(AuthenticationManager.AuthenticationStatus.ACCOUNT_DISABLED, am.authenticateForm(null, realm, formData));
+
+            // Successful authentication
+            realmUser.setEnabled(true);
+            formData = AuthProvidersExternalModelTest.createFormData("realmUser", "pass");
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
         } finally {
             ResteasyProviderFactory.clearContextData();
         }
@@ -149,26 +154,34 @@ public class AuthProvidersLDAPTest extends AbstractModelTest {
             // this is needed for ldap provider
             ResteasyProviderFactory.pushContext(KeycloakRegistry.class, new KeycloakRegistry());
 
+            LdapTestUtils.setLdapPassword(realm, "john", "password");
+
+            // First authenticate successfully to sync john into realm
+            MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password");
+            Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
+
             // Change credential and validate that user can authenticate
             AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
+
+            UserModel john = realm.getUser("john");
             try {
-                authProviderManager.updatePassword("john", "password-updated");
+                Assert.assertTrue(authProviderManager.updatePassword(john, "password-updated"));
             } catch (AuthenticationProviderException ape) {
                 ape.printStackTrace();
                 Assert.fail("Error not expected");
             }
-            MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
+            formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
             Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(null, realm, formData));
 
             // Password updated just in LDAP, so validating directly in realm should fail
-            Assert.assertFalse(realm.validatePassword(realm.getUser("john"), "password-updated"));
+            Assert.assertFalse(realm.validatePassword(john, "password-updated"));
 
             // Switch to not allow updating passwords in ldap
             AuthProvidersExternalModelTest.setPasswordUpdateForProvider(false, AuthProviderConstants.PROVIDER_NAME_PICKETLINK, realm);
 
             // Change credential and validate that password is not updated
             try {
-                authProviderManager.updatePassword("john", "password-updated2");
+                Assert.assertFalse(authProviderManager.updatePassword(john, "password-updated2"));
             } catch (AuthenticationProviderException ape) {
                 ape.printStackTrace();
                 Assert.fail("Error not expected");
diff --git a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
index ce4ca53..fb8ebaa 100755
--- a/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
+++ b/model/tests/src/test/java/org/keycloak/model/test/ImportTest.java
@@ -208,24 +208,9 @@ public class ImportTest extends AbstractModelTest {
         Assert.assertTrue(authProv3.isPasswordUpdateSupported());
 
         // Test authentication linking
-        Set<AuthenticationLinkModel> authLinks = realm.getAuthenticationLinks(socialUser);
-        Assert.assertEquals(2, authLinks.size());
-        boolean plFound = false;
-        boolean extFound = false;
-        for (AuthenticationLinkModel authLinkModel : authLinks) {
-            if (AuthProviderConstants.PROVIDER_NAME_PICKETLINK.equals(authLinkModel.getAuthProvider())) {
-                plFound = true;
-                Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser1");
-            } else if (AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL.equals(authLinkModel.getAuthProvider())) {
-                extFound = true;
-                Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser11");
-            }
-        }
-        Assert.assertTrue(plFound && extFound);
-
-        UserModel foundAuthUser = realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "myUser1"));
-        Assert.assertEquals(foundAuthUser.getLoginName(), socialUser.getLoginName());
-        Assert.assertNull(realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "not-existing")));
+        AuthenticationLinkModel authLink = realm.getAuthenticationLink(socialUser);
+        Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, authLink.getAuthProvider());
+        Assert.assertEquals("myUser1", authLink.getAuthUserId());
 
         commit();
 
diff --git a/model/tests/src/test/resources/testrealm.json b/model/tests/src/test/resources/testrealm.json
index b28087d..56bf23e 100755
--- a/model/tests/src/test/resources/testrealm.json
+++ b/model/tests/src/test/resources/testrealm.json
@@ -75,7 +75,11 @@
         },
         {
             "username": "mySocialUser",
-            "enabled": true
+            "enabled": true,
+            "authenticationLink": {
+                "authProvider": "picketlink",
+                "authUserId": "myUser1"
+            }
         }
     ],
     "socialMappings": [
@@ -100,21 +104,6 @@
             ]
         }
     ],
-    "authenticationMappings": [
-        {
-            "username": "mySocialUser",
-            "authenticationLinks": [
-                {
-                    "authProvider": "picketlink",
-                    "authUserId": "myUser1"
-                },
-                {
-                    "authProvider": "externalModel",
-                    "authUserId": "myUser11"
-                }
-            ]
-        }
-    ],
     "applications": [
         {
             "name": "Application",

pom.xml 14(+10 -4)

diff --git a/pom.xml b/pom.xml
index 5246554..344cb19 100755
--- a/pom.xml
+++ b/pom.xml
@@ -32,6 +32,11 @@
         <jboss.version>7.1.1.Final</jboss.version>
         <wildfly.version>8.0.0.Final</wildfly.version>
         <json.version>20131018</json.version>
+
+        <!-- maven-compiler-plugin -->
+        <maven.compiler.target>1.6</maven.compiler.target>
+        <maven.compiler.source>1.6</maven.compiler.source>
+
     </properties>
 
     <url>http://keycloak.org</url>
@@ -80,6 +85,7 @@
     </contributors>
 
     <modules>
+        <module>audit</module>
         <module>core</module>
         <module>core-jaxrs</module>
         <module>model</module>
@@ -437,8 +443,8 @@
                     <artifactId>maven-compiler-plugin</artifactId>
                     <version>2.3.1</version>
                     <configuration>
-                        <source>1.6</source>
-                        <target>1.6</target>
+                        <source>${maven.compiler.source}</source>
+                        <target>${maven.compiler.target}</target>
                         <encoding>utf-8</encoding>
                     </configuration>
                 </plugin>
@@ -523,8 +529,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                     <encoding>utf-8</encoding>
                 </configuration>
             </plugin>

server/pom.xml 25(+22 -3)

diff --git a/server/pom.xml b/server/pom.xml
index 3a23448..fd5982d 100755
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -8,7 +8,6 @@
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
-    <groupId>org.keycloak</groupId>
     <artifactId>keycloak-server</artifactId>
     <packaging>war</packaging>
     <name>Keycloak Server</name>
@@ -50,6 +49,21 @@
         </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>
             <version>${project.version}</version>
         </dependency>
@@ -183,6 +197,11 @@
             <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>
         </dependency>
@@ -226,8 +245,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>

services/pom.xml 13(+10 -3)

diff --git a/services/pom.xml b/services/pom.xml
index 0571d92..1c68b71 100755
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -38,6 +39,12 @@
         </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-account-api</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -159,8 +166,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
index 51ea351..6b9e50c 100755
--- a/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
+++ b/services/src/main/java/org/keycloak/services/managers/AccessCodeEntry.java
@@ -25,6 +25,8 @@ public class AccessCodeEntry {
     protected String state;
     protected String redirectUri;
     protected boolean rememberMe;
+    protected String authMethod;
+    protected String username;
 
     protected int expiration;
     protected RealmModel realm;
@@ -130,4 +132,20 @@ public class AccessCodeEntry {
     public void setRememberMe(boolean rememberMe) {
         this.rememberMe = rememberMe;
     }
+
+    public String getAuthMethod() {
+        return authMethod;
+    }
+
+    public void setAuthMethod(String authMethod) {
+        this.authMethod = authMethod;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
index 54d2f1f..ea5e5aa 100755
--- a/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
+++ b/services/src/main/java/org/keycloak/services/managers/ApplianceBootstrap.java
@@ -1,8 +1,11 @@
 package org.keycloak.services.managers;
 
+import java.util.Arrays;
+
 import org.jboss.resteasy.logging.Logger;
 import org.keycloak.models.AdminRoles;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.Config;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
@@ -13,6 +16,8 @@ import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.CredentialRepresentation;
 
+import java.util.Collections;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
@@ -56,11 +61,14 @@ public class ApplianceBootstrap {
         realm.setSslNotRequired(true);
         realm.setRegistrationAllowed(false);
         manager.generateRealmKeys(realm);
+        realm.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
 
         ApplicationModel adminConsole = new ApplicationManager(manager).createApplication(realm, Constants.ADMIN_CONSOLE_APPLICATION);
         adminConsole.setBaseUrl("/auth/admin/index.html");
         adminConsole.setEnabled(true);
 
+        realm.setAuditListeners(Collections.singleton("jboss-logging"));
+
         RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
 
         adminConsole.addScope(adminRole);
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index b24de1b..8bb9e53 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -17,8 +17,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.ClientConnection;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.spi.authentication.AuthProviderStatus;
-import org.keycloak.spi.authentication.AuthResult;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
 import org.keycloak.spi.authentication.AuthenticationProviderManager;
 import org.keycloak.util.Time;
 
@@ -222,6 +221,25 @@ public class AuthenticationManager {
 
     protected AuthenticationStatus authenticateInternal(RealmModel realm, MultivaluedMap<String, String> formData, String username) {
         UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
+        if (user == null) {
+            AuthUser authUser = AuthenticationProviderManager.getManager(realm).getUser(username);
+            if (authUser != null) {
+                // Create new user and link him with authentication provider
+                user = realm.addUser(authUser.getUsername());
+                user.setEnabled(true);
+                user.setFirstName(authUser.getFirstName());
+                user.setLastName(authUser.getLastName());
+                user.setEmail(authUser.getEmail());
+                realm.setAuthenticationLink(user, new AuthenticationLinkModel(authUser.getProviderName(), authUser.getId()));
+            } else {
+                logger.warn("User " + username + " not found");
+                return AuthenticationStatus.INVALID_USER;
+            }
+        }
+
+        if (!checkEnabled(user)) {
+            return AuthenticationStatus.ACCOUNT_DISABLED;
+        }
 
         Set<String> types = new HashSet<String>();
 
@@ -236,20 +254,13 @@ public class AuthenticationManager {
                 return AuthenticationStatus.MISSING_PASSWORD;
             }
 
-            if (user == null && types.contains(CredentialRepresentation.TOTP)) {
-                logger.warn("User doesn't exists and TOTP is required for the realm");
-                return AuthenticationStatus.INVALID_USER;
-            }
-
-            if (user != null && user.isTotp()) {
+            if (user.isTotp()) {
                 String token = formData.getFirst(CredentialRepresentation.TOTP);
                 if (token == null) {
                     logger.warn("TOTP token not provided");
                     return AuthenticationStatus.MISSING_TOTP;
                 }
-                if (!checkEnabled(user)) {
-                    return AuthenticationStatus.ACCOUNT_DISABLED;
-                }
+
                 logger.debug("validating TOTP");
                 if (!realm.validateTOTP(user, password, token)) {
                     return AuthenticationStatus.INVALID_CREDENTIALS;
@@ -257,58 +268,12 @@ public class AuthenticationManager {
             } else {
                 logger.debug("validating password for user: " + username);
 
-                AuthResult authResult = AuthenticationProviderManager.getManager(realm).validatePassword(username, password);
-                if (authResult.getAuthProviderStatus() == AuthProviderStatus.INVALID_CREDENTIALS) {
+                AuthProviderStatus authStatus = AuthenticationProviderManager.getManager(realm).validatePassword(user, password);
+                if (authStatus == AuthProviderStatus.INVALID_CREDENTIALS) {
                     logger.debug("invalid password for user: " + username);
                     return AuthenticationStatus.INVALID_CREDENTIALS;
-                } else if (authResult.getAuthProviderStatus() == AuthProviderStatus.USER_NOT_FOUND) {
-                    logger.debug("User " + username + " not found in any Authentication provider");
-                    return AuthenticationStatus.INVALID_USER;
-                }
-
-                if (authResult.getAuthenticatedUser() != null) {
-                    AuthenticatedUser authUser = authResult.getAuthenticatedUser();
-                    AuthenticationLinkModel authLink = new AuthenticationLinkModel(authResult.getProviderName(), authUser.getId());
-                    user = realm.getUserByAuthenticationLink(authLink);
-                    if (user == null) {
-                        user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
-                        if (user != null) {
-                            // Case when we already have user with the same username like authenticated, but he is not yet linked to current provider.
-                            // TODO: Revisit if it's ok to link if we allow to change username. Maybe ask user?
-                            // TODO: Update of existing account?
-                            realm.addAuthenticationLink(user, authLink);
-                            logger.info("User " + authUser.getUsername() + " successfully authenticated and linked with provider " + authResult.getProviderName());
-                        }  else {
-                            // Create new user, which has been successfully authenticated and link him with authentication provider
-                            user = realm.addUser(authUser.getUsername());
-                            user.setEnabled(true);
-                            user.setFirstName(authUser.getFirstName());
-                            user.setLastName(authUser.getLastName());
-                            user.setEmail(authUser.getEmail());
-
-                            realm.addAuthenticationLink(user, authLink);
-                            logger.info("User " + username + " successfully authenticated and created based on provider " + authResult.getProviderName());
-                        }
-                    } else {
-                        // Existing and linked user has been authenticated TODO: Update of existing account?
-                    }
-
-                    // Authenticated username could be different from the "form" username. In this case, we will change it
-                    if (!username.equals(user.getLoginName())) {
-                        formData.putSingle(FORM_USERNAME, user.getLoginName());
-                        logger.debug("Existing user " + user.getLoginName() + " successfully authenticated");
-                    }
-
-                } else {
-                    // Authentication provider didn't send AuthenticatedUser. Using already retrieved user based on username from "form"
-                    if (user == null) {
-                        logger.warn("User '" + username + "' successfully authenticated, but he doesn't exists and don't know how to create him");
-                        return AuthenticationStatus.INVALID_USER;
-                    }
-                }
-
-                if (!checkEnabled(user)) {
-                    return AuthenticationStatus.ACCOUNT_DISABLED;
+                } else if (authStatus == AuthProviderStatus.FAILED) {
+                    return AuthenticationStatus.FAILED;
                 }
             }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index abe3006..d9d40d2 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -21,7 +21,6 @@ import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.ApplicationRepresentation;
 import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
-import org.keycloak.representations.idm.AuthenticationMappingRepresentation;
 import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.OAuthClientRepresentation;
@@ -37,6 +36,7 @@ import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -81,6 +81,8 @@ public class RealmManager {
         setupAdminManagement(realm);
         setupAccountManagement(realm);
 
+        realm.setAuditListeners(Collections.singleton("jboss-logging"));
+
         return realm;
     }
 
@@ -385,15 +387,6 @@ public class RealmManager {
                 }
             }
         }
-        if (rep.getAuthenticationMappings() != null) {
-            for (AuthenticationMappingRepresentation authMapping : rep.getAuthenticationMappings()) {
-                UserModel user = userMap.get(authMapping.getUsername());
-                for (AuthenticationLinkRepresentation link : authMapping.getAuthenticationLinks()) {
-                    AuthenticationLinkModel mappingModel = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId());
-                    newRealm.addAuthenticationLink(user, mappingModel);
-                }
-            }
-        }
 
         if (rep.getSmtpServer() != null) {
             newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
@@ -409,6 +402,9 @@ public class RealmManager {
         if (rep.getAuthenticationProviders() != null) {
             List<AuthenticationProviderModel> authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders());
             newRealm.setAuthenticationProviders(authProviderModels);
+        }  else {
+            List<AuthenticationProviderModel> authProviderModels = Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER);
+            newRealm.setAuthenticationProviders(authProviderModels);
         }
     }
 
@@ -472,6 +468,11 @@ public class RealmManager {
                 newRealm.updateCredential(user, credential);
             }
         }
+        if (userRep.getAuthenticationLink() != null) {
+            AuthenticationLinkRepresentation link = userRep.getAuthenticationLink();
+            AuthenticationLinkModel authLink = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId());
+            newRealm.setAuthenticationLink(user, authLink);
+        }
         return user;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/TokenManager.java b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
index a9e6bcf..73688b8 100755
--- a/services/src/main/java/org/keycloak/services/managers/TokenManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/TokenManager.java
@@ -2,6 +2,8 @@ package org.keycloak.services.managers;
 
 import org.jboss.resteasy.logging.Logger;
 import org.keycloak.OAuthErrorException;
+import org.keycloak.audit.Audit;
+import org.keycloak.audit.Details;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
@@ -98,7 +100,7 @@ public class TokenManager {
         return code;
     }
 
-    public AccessToken refreshAccessToken(RealmModel realm, ClientModel client, String encodedRefreshToken) throws OAuthErrorException {
+    public AccessToken refreshAccessToken(RealmModel realm, ClientModel client, String encodedRefreshToken, Audit audit) throws OAuthErrorException {
         JWSInput jws = new JWSInput(encodedRefreshToken);
         RefreshToken refreshToken = null;
         try {
@@ -117,6 +119,8 @@ public class TokenManager {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale refresh token");
         }
 
+        audit.user(refreshToken.getSubject()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
+
         UserModel user = realm.getUserById(refreshToken.getSubject());
         if (user == null) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user");
@@ -320,8 +324,8 @@ public class TokenManager {
         return encodedToken;
     }
 
-    public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client) {
-        return new AccessTokenResponseBuilder(realm, client);
+    public AccessTokenResponseBuilder responseBuilder(RealmModel realm, ClientModel client, Audit audit) {
+        return new AccessTokenResponseBuilder(realm, client, audit);
     }
 
     public class AccessTokenResponseBuilder {
@@ -330,10 +334,12 @@ public class TokenManager {
         AccessToken accessToken;
         RefreshToken refreshToken;
         IDToken idToken;
+        Audit audit;
 
-        public AccessTokenResponseBuilder(RealmModel realm, ClientModel client) {
+        public AccessTokenResponseBuilder(RealmModel realm, ClientModel client, Audit audit) {
             this.realm = realm;
             this.client = client;
+            this.audit = audit;
         }
 
         public AccessTokenResponseBuilder accessToken(AccessToken accessToken) {
@@ -402,7 +408,21 @@ public class TokenManager {
             return this;
         }
 
+
+
         public AccessTokenResponse build() {
+            if (accessToken != null) {
+                audit.detail(Details.TOKEN_ID, accessToken.getId());
+            }
+
+            if (refreshToken != null) {
+                if (audit.getEvent().getDetails().containsKey(Details.REFRESH_TOKEN_ID)) {
+                    audit.detail(Details.UPDATED_REFRESH_TOKEN_ID, refreshToken.getId());
+                } else {
+                    audit.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
+                }
+            }
+
             AccessTokenResponse res = new AccessTokenResponse();
             if (idToken != null) {
                 String encodedToken = new JWSBuilder().jsonContent(idToken).rsa256(realm.getPrivateKey());
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 8a4da96..b937674 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -27,10 +27,14 @@ import org.keycloak.OAuth2Constants;
 import org.keycloak.account.Account;
 import org.keycloak.account.AccountLoader;
 import org.keycloak.account.AccountPages;
+import org.keycloak.audit.Audit;
+import org.keycloak.audit.Details;
+import org.keycloak.audit.Events;
 import org.keycloak.jaxrs.JaxrsOAuthClient;
 import org.keycloak.models.*;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.managers.AccessCodeEntry;
 import org.keycloak.services.managers.AppAuthManager;
 import org.keycloak.services.managers.Auth;
 import org.keycloak.services.managers.ModelToRepresentation;
@@ -75,11 +79,13 @@ public class AccountService {
 
     private final AppAuthManager authManager;
     private final ApplicationModel application;
+    private Audit audit;
     private final SocialRequestManager socialRequestManager;
 
-    public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager, SocialRequestManager socialRequestManager) {
+    public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager, SocialRequestManager socialRequestManager, Audit audit) {
         this.realm = realm;
         this.application = application;
+        this.audit = audit;
         this.authManager =  new AppAuthManager(KEYCLOAK_ACCOUNT_IDENTITY_COOKIE, tokenManager);
         this.socialRequestManager = socialRequestManager;
     }
@@ -170,8 +176,20 @@ public class AccountService {
 
         user.setFirstName(formData.getFirst("firstName"));
         user.setLastName(formData.getFirst("lastName"));
+
+        String email = formData.getFirst("email");
+        String oldEmail = user.getEmail();
+        boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
+
         user.setEmail(formData.getFirst("email"));
 
+        audit.event(Events.UPDATE_PROFILE).client(auth.getClient()).user(auth.getUser()).success();
+
+        if (emailChanged) {
+            user.setEmailVerified(false);
+            audit.clone().event(Events.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
+        }
+
         return account.setSuccess("accountUpdated").createResponse(AccountPages.ACCOUNT);
     }
 
@@ -184,6 +202,8 @@ public class AccountService {
         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);
     }
@@ -215,6 +235,8 @@ public class AccountService {
 
         user.setTotp(true);
 
+        audit.event(Events.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
+
         return account.setSuccess("successTotp").createResponse(AccountPages.TOTP);
     }
 
@@ -242,17 +264,21 @@ public class AccountService {
         AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
         if (Validation.isEmpty(password)) {
             return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
-            // TODO: This may not work in some cases. For example if ldap username is "foo" but actual loginName of user is "bar", which could theoretically happen...
-        } else if (authProviderManager.validatePassword(user.getLoginName(), password).getAuthProviderStatus() != AuthProviderStatus.SUCCESS) {
+        } else if (authProviderManager.validatePassword(user, password) != AuthProviderStatus.SUCCESS) {
             return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
         }
 
         try {
-            authProviderManager.updatePassword(user.getLoginName(), passwordNew);
+            boolean passwordUpdateSuccess = authProviderManager.updatePassword(user, passwordNew);
+            if (!passwordUpdateSuccess) {
+                return account.setError("Password update failed").createResponse(AccountPages.PASSWORD);
+            }
         } catch (AuthenticationProviderException ape) {
             return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
         }
 
+        audit.event(Events.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
+
         return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
     }
 
@@ -298,8 +324,16 @@ public class AccountService {
                     return account.setError(Messages.SOCIAL_REDIRECT_ERROR).createResponse(AccountPages.SOCIAL);
                 }
             case REMOVE:
-                if (realm.removeSocialLink(user, providerId)) {
+                SocialLinkModel link = realm.getSocialLink(user, providerId);
+                if (link != null) {
+                    realm.removeSocialLink(user, providerId);
+
                     logger.debug("Social provider " + providerId + " removed successfully from user " + user.getLoginName());
+
+                    audit.event(Events.REMOVE_SOCIAL_LINK).client(auth.getClient()).user(auth.getUser())
+                            .detail(Details.USERNAME, link.getSocialUserId() + "@" + link.getSocialProvider())
+                            .success();
+
                     return account.setSuccess(Messages.SOCIAL_PROVIDER_REMOVED).createResponse(AccountPages.SOCIAL);
                 } else {
                     return account.setError(Messages.SOCIAL_LINK_NOT_ACTIVE).createResponse(AccountPages.SOCIAL);
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
index 26e4888..49ff5d7 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthFlows.java
@@ -24,23 +24,22 @@ package org.keycloak.services.resources.flows;
 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.Details;
+import org.keycloak.audit.Events;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
-import org.keycloak.models.OAuthClientModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
-import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.services.managers.AccessCodeEntry;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.TokenManager;
-import org.keycloak.services.resources.TokenService;
 import org.keycloak.util.Time;
 
-import javax.ws.rs.Path;
 import javax.ws.rs.core.Cookie;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
@@ -56,15 +55,15 @@ public class OAuthFlows {
 
     private static final Logger log = Logger.getLogger(OAuthFlows.class);
 
-    private RealmModel realm;
+    private final RealmModel realm;
 
-    private HttpRequest request;
+    private final HttpRequest request;
 
-    private UriInfo uriInfo;
+    private final UriInfo uriInfo;
 
-    private AuthenticationManager authManager;
+    private final AuthenticationManager authManager;
 
-    private TokenManager tokenManager;
+    private final TokenManager tokenManager;
 
     OAuthFlows(RealmModel realm, HttpRequest request, UriInfo uriInfo, AuthenticationManager authManager,
             TokenManager tokenManager) {
@@ -110,28 +109,40 @@ public class OAuthFlows {
         }
     }
 
-    public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user) {
-        return processAccessCode(scopeParam, state, redirect, client, user, false);
+    public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, Audit audit) {
+        return processAccessCode(scopeParam, state, redirect, client, user, null, false, "form", audit);
     }
 
 
-    public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, boolean rememberMe) {
+    public Response processAccessCode(String scopeParam, String state, String redirect, ClientModel client, UserModel user, String username, boolean rememberMe, String authMethod, Audit audit) {
         isTotpConfigurationRequired(user);
         isEmailVerificationRequired(user);
 
         boolean isResource = client instanceof ApplicationModel;
         AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
+        accessCode.setUsername(username);
+        accessCode.setRememberMe(rememberMe);
+        accessCode.setAuthMethod(authMethod);
+
         log.debug("processAccessCode: isResource: {0}", isResource);
         log.debug("processAccessCode: go to oauth page?: {0}",
                 (!isResource && (accessCode.getRealmRolesRequested().size() > 0 || accessCode.getResourceRolesRequested()
                         .size() > 0)));
 
+        audit.detail(Details.CODE_ID, accessCode.getId());
+
         Set<RequiredAction> requiredActions = user.getRequiredActions();
         if (!requiredActions.isEmpty()) {
             accessCode.setRequiredActions(new HashSet<UserModel.RequiredAction>(requiredActions));
             accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
+
+            RequiredAction action = user.getRequiredActions().iterator().next();
+            if (action.equals(RequiredAction.VERIFY_EMAIL)) {
+                audit.clone().event(Events.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
+            }
+
             return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(user)
-                    .createResponse(user.getRequiredActions().iterator().next());
+                    .createResponse(action);
         }
 
         if (!isResource
@@ -143,6 +154,7 @@ public class OAuthFlows {
         }
 
         if (redirect != null) {
+            audit.success();
             return redirectAccessCode(accessCode, state, redirect, rememberMe);
         } else {
             return null;
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 96d2c3f..4601559 100755
--- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java
@@ -1,6 +1,7 @@
 package org.keycloak.services.resources;
 
 import org.jboss.resteasy.logging.Logger;
+import org.keycloak.audit.Audit;
 import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
@@ -9,6 +10,7 @@ import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.SocialRequestManager;
 import org.keycloak.services.managers.TokenManager;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.NotFoundException;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
@@ -38,6 +40,9 @@ public class RealmsResource {
     @Context
     protected KeycloakSession session;
 
+    @Context
+    protected HttpServletRequest servletRequest;
+
     protected TokenManager tokenManager;
     protected SocialRequestManager socialRequestManager;
 
@@ -54,7 +59,8 @@ public class RealmsResource {
     public TokenService getTokenService(final @PathParam("realm") String name) {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = locateRealm(name, realmManager);
-        TokenService tokenService = new TokenService(realm, tokenManager);
+        Audit audit = Audit.create(realm, servletRequest.getRemoteAddr());
+        TokenService tokenService = new TokenService(realm, tokenManager, audit);
         resourceContext.initResource(tokenService);
         return tokenService;
     }
@@ -78,7 +84,9 @@ public class RealmsResource {
             throw new NotFoundException();
         }
 
-        AccountService accountService = new AccountService(realm, application, tokenManager, socialRequestManager);
+        Audit audit = Audit.create(realm, servletRequest.getRemoteAddr());
+
+        AccountService accountService = new AccountService(realm, application, tokenManager, socialRequestManager, audit);
         resourceContext.initResource(accountService);
         return accountService;
     }
@@ -92,5 +100,4 @@ public class RealmsResource {
         return realmResource;
     }
 
-
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
index c070723..d77ba7e 100755
--- a/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/RequiredActionsService.java
@@ -24,6 +24,10 @@ package org.keycloak.services.resources;
 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.Details;
+import org.keycloak.audit.Errors;
+import org.keycloak.audit.Events;
 import org.keycloak.login.LoginForms;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
@@ -84,9 +88,12 @@ public class RequiredActionsService {
 
     private TokenManager tokenManager;
 
-    public RequiredActionsService(RealmModel realm, TokenManager tokenManager) {
+    private Audit audit;
+
+    public RequiredActionsService(RealmModel realm, TokenManager tokenManager, Audit audit) {
         this.realm = realm;
         this.tokenManager = tokenManager;
+        this.audit = audit;
     }
 
     @Path("profile")
@@ -100,6 +107,8 @@ public class RequiredActionsService {
 
         UserModel user = getUser(accessCode);
 
+        initAudit(accessCode);
+
         String error = Validation.validateUpdateProfileForm(formData);
         if (error != null) {
             return Flows.forms(realm, request, uriInfo).setUser(user).setError(error).createResponse(RequiredAction.UPDATE_PROFILE);
@@ -107,11 +116,22 @@ public class RequiredActionsService {
 
         user.setFirstName(formData.getFirst("firstName"));
         user.setLastName(formData.getFirst("lastName"));
-        user.setEmail(formData.getFirst("email"));
+
+        String email = formData.getFirst("email");
+        String oldEmail = user.getEmail();
+        boolean emailChanged = oldEmail != null ? !oldEmail.equals(email) : email != null;
+
+        user.setEmail(email);
 
         user.removeRequiredAction(RequiredAction.UPDATE_PROFILE);
         accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PROFILE);
 
+        audit.clone().event(Events.UPDATE_PROFILE).success();
+        if (emailChanged) {
+            user.setEmailVerified(false);
+            audit.clone().event(Events.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
+        }
+
         return redirectOauth(user, accessCode);
     }
 
@@ -126,6 +146,8 @@ public class RequiredActionsService {
 
         UserModel user = getUser(accessCode);
 
+        initAudit(accessCode);
+
         String totp = formData.getFirst("totp");
         String totpSecret = formData.getFirst("totpSecret");
 
@@ -146,6 +168,8 @@ public class RequiredActionsService {
         user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP);
         accessCode.getRequiredActions().remove(RequiredAction.CONFIGURE_TOTP);
 
+        audit.clone().event(Events.UPDATE_TOTP).success();
+
         return redirectOauth(user, accessCode);
     }
 
@@ -163,6 +187,8 @@ public class RequiredActionsService {
 
         UserModel user = getUser(accessCode);
 
+        initAudit(accessCode);
+
         String passwordNew = formData.getFirst("password-new");
         String passwordConfirm = formData.getFirst("password-confirm");
 
@@ -174,7 +200,10 @@ public class RequiredActionsService {
         }
 
         try {
-            AuthenticationProviderManager.getManager(realm).updatePassword(user.getLoginName(), passwordNew);
+            boolean updateSuccessful = AuthenticationProviderManager.getManager(realm).updatePassword(user, passwordNew);
+            if (!updateSuccessful) {
+                return loginForms.setError("Password update failed").createResponse(RequiredAction.UPDATE_PASSWORD);
+            }
         } catch (AuthenticationProviderException ape) {
             return loginForms.setError(ape.getMessage()).createResponse(RequiredAction.UPDATE_PASSWORD);
         }
@@ -186,6 +215,8 @@ public class RequiredActionsService {
             accessCode.getRequiredActions().remove(RequiredAction.UPDATE_PASSWORD);
         }
 
+        audit.clone().event(Events.UPDATE_PASSWORD).success();
+
         return redirectOauth(user, accessCode);
     }
 
@@ -201,11 +232,16 @@ public class RequiredActionsService {
             }
 
             UserModel user = getUser(accessCode);
+
+            initAudit(accessCode);
+
             user.setEmailVerified(true);
 
             user.removeRequiredAction(RequiredAction.VERIFY_EMAIL);
             accessCode.getRequiredActions().remove(RequiredAction.VERIFY_EMAIL);
 
+            audit.clone().event(Events.VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
+
             return redirectOauth(user, accessCode);
         } else {
             AccessCodeEntry accessCode = getAccessCodeEntry(RequiredAction.VERIFY_EMAIL);
@@ -213,6 +249,9 @@ public class RequiredActionsService {
                 return unauthorized();
             }
 
+            initAudit(accessCode);
+            //audit.clone().event(Events.SEND_VERIFY_EMAIL).detail(Details.EMAIL, accessCode.getUser().getEmail()).success();
+
             return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).setUser(accessCode.getUser())
                     .createResponse(RequiredAction.VERIFY_EMAIL);
         }
@@ -223,10 +262,12 @@ public class RequiredActionsService {
     public Response passwordReset() {
         if (uriInfo.getQueryParameters().containsKey("key")) {
             AccessCodeEntry accessCode = tokenManager.getAccessCode(uriInfo.getQueryParameters().getFirst("key"));
+            accessCode.setAuthMethod("form");
             if (accessCode == null || accessCode.isExpired()
                     || !accessCode.getRequiredActions().contains(RequiredAction.UPDATE_PASSWORD)) {
                 return unauthorized();
             }
+
             return Flows.forms(realm, request, uriInfo).setAccessCode(accessCode.getId(), accessCode.getCode()).createResponse(RequiredAction.UPDATE_PASSWORD);
         } else {
             return Flows.forms(realm, request, uriInfo).createPasswordReset();
@@ -254,6 +295,12 @@ public class RequiredActionsService {
                     "Login requester not enabled.");
         }
 
+        audit.event(Events.SEND_RESET_PASSWORD).client(clientId)
+                .detail(Details.REDIRECT_URI, redirect)
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "form")
+                .detail(Details.USERNAME, username);
+
         UserModel user = realm.getUser(username);
         if (user == null && username.contains("@")) {
             user = realm.getUserByEmail(username);
@@ -261,6 +308,7 @@ public class RequiredActionsService {
 
         if (user == null) {
             logger.warn("Failed to send password reset email: user not found");
+            audit.error(Errors.USER_NOT_FOUND);
         } else {
             Set<RequiredAction> requiredActions = new HashSet<RequiredAction>(user.getRequiredActions());
             requiredActions.add(RequiredAction.UPDATE_PASSWORD);
@@ -268,9 +316,12 @@ public class RequiredActionsService {
             AccessCodeEntry accessCode = tokenManager.createAccessCode(scopeParam, state, redirect, realm, client, user);
             accessCode.setRequiredActions(requiredActions);
             accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespanUserAction());
+            accessCode.setAuthMethod("form");
+            accessCode.setUsername(username);
 
             try {
                 new EmailSender(realm.getSmtpConfig()).sendPasswordReset(user, realm, accessCode, uriInfo);
+                audit.user(user).detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, accessCode.getId()).success();
             } catch (EmailException e) {
                 logger.error("Failed to send password reset email", e);
                 return Flows.forms(realm, request, uriInfo).setError("emailSendError").createErrorPage();
@@ -339,11 +390,27 @@ public class RequiredActionsService {
         } else {
             logger.debug("redirectOauth: redirecting to: {0}", accessCode.getRedirectUri());
             accessCode.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
+
+            audit.success();
             return Flows.oauth(realm, request, uriInfo, authManager, tokenManager).redirectAccessCode(accessCode,
                     accessCode.getState(), accessCode.getRedirectUri());
         }
     }
 
+    private void initAudit(AccessCodeEntry accessCode) {
+        audit.event(Events.LOGIN).client(accessCode.getClient())
+                .user(accessCode.getUser())
+                .detail(Details.CODE_ID, accessCode.getId())
+                .detail(Details.REDIRECT_URI, accessCode.getRedirectUri())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, accessCode.getAuthMethod())
+                .detail(Details.USERNAME, accessCode.getUsername());
+
+        if (accessCode.isRememberMe()) {
+            audit.detail(Details.REMEMBER_ME, "true");
+        }
+    }
+
     private Response unauthorized() {
         return Flows.forms(realm, request, uriInfo).setError("Unauthorized request").createErrorPage();
     }
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 45f9a0e..3d5bfa5 100755
--- a/services/src/main/java/org/keycloak/services/resources/SocialResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/SocialResource.java
@@ -24,6 +24,10 @@ package org.keycloak.services.resources;
 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.Details;
+import org.keycloak.audit.Errors;
+import org.keycloak.audit.Events;
 import org.keycloak.models.AccountRoles;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
@@ -48,6 +52,7 @@ import org.keycloak.social.SocialProviderConfig;
 import org.keycloak.social.SocialProviderException;
 import org.keycloak.social.SocialUser;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
@@ -89,6 +94,8 @@ public class SocialResource {
     @Context
     protected KeycloakSession session;
 
+    @Context
+    protected HttpServletRequest servletRequest;
 
     private SocialRequestManager socialRequestManager;
 
@@ -114,19 +121,33 @@ public class SocialResource {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = realmManager.getRealmByName(realmName);
 
+        Audit audit = Audit.create(realm, servletRequest.getRemoteAddr())
+                .event(Events.LOGIN)
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "social");
+
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
         if (!realm.isEnabled()) {
+            audit.error(Errors.REALM_DISABLED);
             return oauth.forwardToSecurityFailure("Realm not enabled.");
         }
 
         String clientId = requestData.getClientAttributes().get("clientId");
+        String redirectUri = requestData.getClientAttribute("redirectUri");
+        String scope = requestData.getClientAttributes().get(OAuth2Constants.SCOPE);
+        String state = requestData.getClientAttributes().get(OAuth2Constants.STATE);
+        String responseType = requestData.getClientAttribute("responseType");
+
+        audit.client(clientId).detail(Details.REDIRECT_URI, redirectUri);
 
         ClientModel client = realm.findClient(clientId);
         if (client == null) {
+            audit.error(Errors.CLIENT_NOT_FOUND);
             return oauth.forwardToSecurityFailure("Unknown login requester.");
         }
         if (!client.isEnabled()) {
+            audit.error(Errors.CLIENT_DISABLED);
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
@@ -142,17 +163,21 @@ public class SocialResource {
             socialUser = provider.processCallback(config, callback);
         } catch (SocialAccessDeniedException e) {
             MultivaluedHashMap<String, String> queryParms = new MultivaluedHashMap<String, String>();
-            queryParms.putSingle(OAuth2Constants.CLIENT_ID, requestData.getClientAttribute("clientId"));
-            queryParms.putSingle(OAuth2Constants.STATE, requestData.getClientAttribute(OAuth2Constants.STATE));
-            queryParms.putSingle(OAuth2Constants.SCOPE, requestData.getClientAttribute(OAuth2Constants.SCOPE));
-            queryParms.putSingle(OAuth2Constants.REDIRECT_URI, requestData.getClientAttribute("redirectUri"));
-            queryParms.putSingle(OAuth2Constants.RESPONSE_TYPE, requestData.getClientAttribute("responseType"));
+            queryParms.putSingle(OAuth2Constants.CLIENT_ID, clientId);
+            queryParms.putSingle(OAuth2Constants.STATE, state);
+            queryParms.putSingle(OAuth2Constants.SCOPE, scope);
+            queryParms.putSingle(OAuth2Constants.REDIRECT_URI, redirectUri);
+            queryParms.putSingle(OAuth2Constants.RESPONSE_TYPE, responseType);
+
+            audit.error(Errors.REJECTED_BY_USER);
             return  Flows.forms(realm, request, uriInfo).setQueryParams(queryParms).setWarning("Access denied").createLogin();
         } catch (SocialProviderException e) {
-            logger.warn("Failed to process social callback", e);
+            logger.error("Failed to process social callback", e);
             return oauth.forwardToSecurityFailure("Failed to process social callback");
         }
 
+        audit.detail(Details.USERNAME, socialUser.getId() + "@" + provider.getId());
+
         SocialLinkModel socialLink = new SocialLinkModel(provider.getId(), socialUser.getId(), socialUser.getUsername());
         UserModel user = realm.getUserBySocialLink(socialLink);
 
@@ -161,30 +186,39 @@ public class SocialResource {
         if (userId != null) {
             UserModel authenticatedUser = realm.getUserById(userId);
 
+            audit.event(Events.SOCIAL_LINK).user(userId);
+
             if (user != null) {
+                audit.error(Errors.SOCIAL_ID_IN_USE);
                 return oauth.forwardToSecurityFailure("This social account is already linked to other user");
             }
 
             if (!authenticatedUser.isEnabled()) {
+                audit.error(Errors.USER_DISABLED);
                 return oauth.forwardToSecurityFailure("User is disabled");
             }
+
             if (!realm.hasRole(authenticatedUser, realm.getApplicationByName(Constants.ACCOUNT_MANAGEMENT_APP).getRole(AccountRoles.MANAGE_ACCOUNT))) {
+                audit.error(Errors.NOT_ALLOWED);
                 return oauth.forwardToSecurityFailure("Insufficient permissions to link social account");
             }
 
-            realm.addSocialLink(authenticatedUser, socialLink);
-            logger.debug("Social provider " + provider.getId() + " linked with user " + authenticatedUser.getLoginName());
-
-            String redirectUri = requestData.getClientAttributes().get("redirectUri");
             if (redirectUri == null) {
+                audit.error(Errors.INVALID_REDIRECT_URI);
                 return oauth.forwardToSecurityFailure("Unknown redirectUri");
             }
 
+            realm.addSocialLink(authenticatedUser, socialLink);
+            logger.debug("Social provider " + provider.getId() + " linked with user " + authenticatedUser.getLoginName());
+
+            audit.success();
             return Response.status(Status.FOUND).location(UriBuilder.fromUri(redirectUri).build()).build();
         }
 
         if (user == null) {
+
             if (!realm.isRegistrationAllowed()) {
+                audit.error(Errors.REGISTRATION_DISABLED);
                 return oauth.forwardToSecurityFailure("Registration not allowed");
             }
 
@@ -199,17 +233,22 @@ public class SocialResource {
             }
 
             realm.addSocialLink(user, socialLink);
+
+            audit.clone().user(user).event(Events.REGISTER)
+                    .detail(Details.REGISTER_METHOD, "social")
+                    .detail(Details.EMAIL, socialUser.getEmail())
+                    .removeDetail("auth_method")
+                    .success();
         }
 
+        audit.user(user);
+
         if (!user.isEnabled()) {
+            audit.error(Errors.USER_DISABLED);
             return oauth.forwardToSecurityFailure("Your account is not enabled.");
         }
 
-        String scope = requestData.getClientAttributes().get(OAuth2Constants.SCOPE);
-        String state = requestData.getClientAttributes().get(OAuth2Constants.STATE);
-        String redirectUri = requestData.getClientAttributes().get("redirectUri");
-
-        return oauth.processAccessCode(scope, state, redirectUri, client, user);
+        return oauth.processAccessCode(scope, state, redirectUri, client, user, socialLink.getSocialUserId() + "@" + socialLink.getSocialProvider(), false, "social", audit);
     }
 
     @GET
@@ -221,23 +260,33 @@ public class SocialResource {
         RealmManager realmManager = new RealmManager(session);
         RealmModel realm = realmManager.getRealmByName(realmName);
 
+        Audit audit = Audit.create(realm, servletRequest.getRemoteAddr())
+                .event(Events.LOGIN).client(clientId)
+                .detail(Details.REDIRECT_URI, redirectUri)
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "social");
+
         SocialProvider provider = SocialLoader.load(providerId);
         if (provider == null) {
+            audit.error(Errors.SOCIAL_PROVIDER_NOT_FOUND);
             return Flows.forms(realm, request, uriInfo).setError("Social provider not found").createErrorPage();
         }
 
         ClientModel client = realm.findClient(clientId);
         if (client == null) {
+            audit.error(Errors.CLIENT_NOT_FOUND);
             logger.warn("Unknown login requester: " + clientId);
             return Flows.forms(realm, request, uriInfo).setError("Unknown login requester.").createErrorPage();
         }
 
         if (!client.isEnabled()) {
+            audit.error(Errors.CLIENT_DISABLED);
             logger.warn("Login requester not enabled.");
             return Flows.forms(realm, request, uriInfo).setError("Login requester not enabled.").createErrorPage();
         }
         redirectUri = TokenService.verifyRedirectUri(redirectUri, client);
         if (redirectUri == null) {
+            audit.error(Errors.INVALID_REDIRECT_URI);
             return Flows.forms(realm, request, uriInfo).setError("Invalid redirect_uri.").createErrorPage();
         }
 
@@ -248,6 +297,7 @@ public class SocialResource {
                     .putClientAttribute(OAuth2Constants.STATE, state).putClientAttribute("redirectUri", redirectUri)
                     .putClientAttribute("responseType", responseType).redirectToSocialProvider();
         } catch (Throwable t) {
+            logger.error("Failed to redirect to social auth", t);
             return Flows.forms(realm, request, uriInfo).setError("Failed to redirect to social auth").createErrorPage();
         }
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/TokenService.java b/services/src/main/java/org/keycloak/services/resources/TokenService.java
index 63f8d98..b326216 100755
--- a/services/src/main/java/org/keycloak/services/resources/TokenService.java
+++ b/services/src/main/java/org/keycloak/services/resources/TokenService.java
@@ -6,6 +6,10 @@ import org.jboss.resteasy.spi.HttpRequest;
 import org.jboss.resteasy.spi.HttpResponse;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.OAuthErrorException;
+import org.keycloak.audit.Audit;
+import org.keycloak.audit.Details;
+import org.keycloak.audit.Errors;
+import org.keycloak.audit.Events;
 import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.jose.jws.crypto.RSAProvider;
 import org.keycloak.models.ClientModel;
@@ -74,6 +78,7 @@ public class TokenService {
 
     protected RealmModel realm;
     protected TokenManager tokenManager;
+    private Audit audit;
     protected AuthenticationManager authManager = new AuthenticationManager();
 
     @Context
@@ -100,9 +105,10 @@ public class TokenService {
 
     private ResourceAdminManager resourceAdminManager = new ResourceAdminManager();
 
-    public TokenService(RealmModel realm, TokenManager tokenManager) {
+    public TokenService(RealmModel realm, TokenManager tokenManager, Audit audit) {
         this.realm = realm;
         this.tokenManager = tokenManager;
+        this.audit = audit;
     }
 
     public static UriBuilder tokenServiceBaseUrl(UriInfo uriInfo) {
@@ -146,31 +152,42 @@ public class TokenService {
             throw new NotAcceptableException("HTTPS required");
         }
 
-        ClientModel client = authorizeClient(authorizationHeader, form);
+        audit.event(Events.LOGIN).detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token");
+
+        ClientModel client = authorizeClient(authorizationHeader, form, audit);
 
         if (client.isPublicClient()) {
             // we don't allow public clients to invoke grants/access to prevent phishing attacks
+            audit.error(Errors.NOT_ALLOWED);
             throw new ForbiddenException("Public clients are not allowed to invoke grants/access");
         }
 
-
-        if (form.getFirst(AuthenticationManager.FORM_USERNAME) == null) {
+        String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
+        if (username == null) {
+            audit.error(Errors.USERNAME_MISSING);
             throw new NotAuthorizedException("No username");
         }
+        audit.detail(Details.USERNAME, username);
         if (!realm.isEnabled()) {
+            audit.error(Errors.REALM_DISABLED);
             throw new NotAuthorizedException("Disabled realm");
         }
 
         if (authManager.authenticateForm(clientConnection, realm, form) != AuthenticationStatus.SUCCESS) {
+            audit.error(Errors.INVALID_USER_CREDENTIALS);
             throw new NotAuthorizedException("Auth failed");
         }
 
         UserModel user = realm.getUser(form.getFirst(AuthenticationManager.FORM_USERNAME));
         String scope = form.getFirst(OAuth2Constants.SCOPE);
-        AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
+
+        AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
                 .generateAccessToken(scope, client, user)
                 .generateIDToken()
                 .build();
+
+        audit.success();
+
         return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
     }
 
@@ -185,22 +202,28 @@ public class TokenService {
             throw new NotAcceptableException("HTTPS required");
         }
 
-        ClientModel client = authorizeClient(authorizationHeader, form);
+        audit.event(Events.REFRESH_TOKEN);
+
+        ClientModel client = authorizeClient(authorizationHeader, form, audit);
         String refreshToken = form.getFirst(OAuth2Constants.REFRESH_TOKEN);
         AccessToken accessToken = null;
         try {
-            accessToken = tokenManager.refreshAccessToken(realm, client, refreshToken);
+            accessToken = tokenManager.refreshAccessToken(realm, client, refreshToken, audit);
         } catch (OAuthErrorException e) {
             Map<String, String> error = new HashMap<String, String>();
             error.put(OAuth2Constants.ERROR, e.getError());
             if (e.getDescription() != null) error.put(OAuth2Constants.ERROR_DESCRIPTION, e.getDescription());
+            audit.error(Errors.INVALID_TOKEN);
             throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build(), e);
         }
 
-        AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
+        AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
                 .accessToken(accessToken)
                 .generateIDToken()
                 .generateRefreshToken().build();
+
+        audit.success();
+
         return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();
     }
 
@@ -211,6 +234,23 @@ public class TokenService {
                                  @QueryParam("state") final String state, @QueryParam("redirect_uri") String redirect,
                                  final MultivaluedMap<String, String> formData) {
         logger.debug("TokenService.processLogin");
+
+        String username = formData.getFirst(AuthenticationManager.FORM_USERNAME);
+
+        String rememberMe = formData.getFirst("rememberMe");
+        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
+        logger.debug("*** Remember me: " + remember);
+
+        audit.event(Events.LOGIN).client(clientId)
+                .detail(Details.REDIRECT_URI, redirect)
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "form")
+                .detail(Details.USERNAME, username);
+
+        if (remember) {
+            audit.detail(Details.REMEMBER_ME, "true");
+        }
+
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
         if (!checkSsl()) {
@@ -218,30 +258,32 @@ public class TokenService {
         }
 
         if (!realm.isEnabled()) {
+            audit.error(Errors.REALM_DISABLED);
             return oauth.forwardToSecurityFailure("Realm not enabled.");
         }
         ClientModel client = realm.findClient(clientId);
         if (client == null) {
+            audit.error(Errors.CLIENT_NOT_FOUND);
             return oauth.forwardToSecurityFailure("Unknown login requester.");
         }
         if (!client.isEnabled()) {
+            audit.error(Errors.CLIENT_NOT_FOUND);
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
         redirect = verifyRedirectUri(redirect, client);
         if (redirect == null) {
+            audit.error(Errors.INVALID_REDIRECT_URI);
             return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
         }
 
         if (formData.containsKey("cancel")) {
+            audit.error(Errors.REJECTED_BY_USER);
             return oauth.redirectError(client, "access_denied", state, redirect);
         }
 
         AuthenticationStatus status = authManager.authenticateForm(clientConnection, realm, formData);
 
-        String rememberMe = formData.getFirst("rememberMe");
-        boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
-        logger.debug("*** Remember me: " + remember);
         if (remember) {
             NewCookie cookie = authManager.createRememberMeCookie(realm, uriInfo);
             response.addNewCookie(cookie);
@@ -252,20 +294,26 @@ public class TokenService {
         switch (status) {
             case SUCCESS:
             case ACTIONS_REQUIRED:
-                UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, formData.getFirst(AuthenticationManager.FORM_USERNAME));
-                return oauth.processAccessCode(scopeParam, state, redirect, client, user, remember);
+                UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
+		        audit.user(user);
+                return oauth.processAccessCode(scopeParam, state, redirect, client, user, username, remember, "form", audit);
             case ACCOUNT_DISABLED:
+                audit.error(Errors.USER_DISABLED);
                 return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
             case MISSING_TOTP:
                 return Flows.forms(realm, request, uriInfo).setFormData(formData).createLoginTotp();
+            case INVALID_USER:
+                audit.error(Errors.USER_NOT_FOUND);
+                return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
             default:
+                audit.error(Errors.INVALID_USER_CREDENTIALS);
                 return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
         }
     }
 
     @Path("auth/request/login-actions")
     public RequiredActionsService getRequiredActionsService() {
-        RequiredActionsService service = new RequiredActionsService(realm, tokenManager);
+        RequiredActionsService service = new RequiredActionsService(realm, tokenManager, audit);
         resourceContext.initResource(service);
         return service;
     }
@@ -276,30 +324,46 @@ public class TokenService {
     public Response processRegister(@QueryParam("client_id") final String clientId,
                                     @QueryParam("scope") final String scopeParam, @QueryParam("state") final String state,
                                     @QueryParam("redirect_uri") String redirect, final MultivaluedMap<String, String> formData) {
+
+        String username = formData.getFirst("username");
+        String email = formData.getFirst("email");
+
+        audit.event(Events.REGISTER).client(clientId)
+                .detail(Details.REDIRECT_URI, redirect)
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.USERNAME, username)
+                .detail(Details.EMAIL, email)
+                .detail(Details.REGISTER_METHOD, "form");
+
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
         if (!realm.isEnabled()) {
             logger.warn("Realm not enabled");
+            audit.error(Errors.REALM_DISABLED);
             return oauth.forwardToSecurityFailure("Realm not enabled");
         }
         ClientModel client = realm.findClient(clientId);
         if (client == null) {
             logger.warn("Unknown login requester.");
+            audit.error(Errors.CLIENT_NOT_FOUND);
             return oauth.forwardToSecurityFailure("Unknown login requester.");
         }
 
         if (!client.isEnabled()) {
             logger.warn("Login requester not enabled.");
+            audit.error(Errors.CLIENT_DISABLED);
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
         redirect = verifyRedirectUri(redirect, client);
         if (redirect == null) {
+            audit.error(Errors.INVALID_REDIRECT_URI);
             return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
         }
 
         if (!realm.isRegistrationAllowed()) {
             logger.warn("Registration not allowed");
+            audit.error(Errors.REGISTRATION_DISABLED);
             return oauth.forwardToSecurityFailure("Registration not allowed");
         }
 
@@ -315,13 +379,13 @@ public class TokenService {
         }
 
         if (error != null) {
+            audit.error(Errors.INVALID_REGISTRATION);
             return Flows.forms(realm, request, uriInfo).setError(error).setFormData(formData).createRegistration();
         }
 
-        String username = formData.getFirst("username");
-
         UserModel user = realm.getUser(username);
         if (user != null) {
+            audit.error(Errors.USERNAME_IN_USE);
             return Flows.forms(realm, request, uriInfo).setError(Messages.USERNAME_EXISTS).setFormData(formData).createRegistration();
         }
 
@@ -330,21 +394,33 @@ public class TokenService {
         user.setFirstName(formData.getFirst("firstName"));
         user.setLastName(formData.getFirst("lastName"));
 
-        user.setEmail(formData.getFirst("email"));
+        user.setEmail(email);
 
         if (requiredCredentialTypes.contains(CredentialRepresentation.PASSWORD)) {
             UserCredentialModel credentials = new UserCredentialModel();
             credentials.setType(CredentialRepresentation.PASSWORD);
             credentials.setValue(formData.getFirst("password"));
+
+            boolean passwordUpdateSuccessful;
+            String passwordUpdateError = null;
             try {
-                AuthenticationProviderManager.getManager(realm).updatePassword(username, formData.getFirst("password"));
+                passwordUpdateSuccessful = AuthenticationProviderManager.getManager(realm).updatePassword(user, formData.getFirst("password"));
+                passwordUpdateError = "Password update failed";
             } catch (AuthenticationProviderException ape) {
-                // User already registered, but force him to update password
+                passwordUpdateSuccessful = false;
+                passwordUpdateError = ape.getMessage();
+            }
+
+            // User already registered, but force him to update password
+            if (!passwordUpdateSuccessful) {
                 user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
-                return Flows.forms(realm, request, uriInfo).setError(ape.getMessage()).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
+                return Flows.forms(realm, request, uriInfo).setError(passwordUpdateError).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
             }
         }
 
+        audit.user(user).success();
+        audit.reset();
+
         return processLogin(clientId, scopeParam, state, redirect, formData);
     }
 
@@ -365,19 +441,20 @@ public class TokenService {
             throw new NotAcceptableException("HTTPS required");
         }
 
+        audit.event(Events.CODE_TO_TOKEN);
+
         if (!realm.isEnabled()) {
+            audit.error(Errors.REALM_DISABLED);
             throw new NotAuthorizedException("Realm not enabled");
         }
 
-        ClientModel client = authorizeClient(authorizationHeader, formData);
-
         String code = formData.getFirst(OAuth2Constants.CODE);
         if (code == null) {
             Map<String, String> error = new HashMap<String, String>();
             error.put(OAuth2Constants.ERROR, "invalid_request");
             error.put(OAuth2Constants.ERROR_DESCRIPTION, "code not specified");
+            audit.error(Errors.INVALID_CODE);
             throw new BadRequestException("Code not specified", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
-
         }
 
         JWSInput input = new JWSInput(code);
@@ -391,22 +468,33 @@ public class TokenService {
             Map<String, String> res = new HashMap<String, String>();
             res.put(OAuth2Constants.ERROR, "invalid_grant");
             res.put(OAuth2Constants.ERROR_DESCRIPTION, "Unable to verify code signature");
+            audit.error(Errors.INVALID_CODE);
             return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
                     .build();
         }
         String key = input.readContentAsString();
+
+        audit.detail(Details.CODE_ID, key);
+
         AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
         if (accessCode == null) {
             Map<String, String> res = new HashMap<String, String>();
             res.put(OAuth2Constants.ERROR, "invalid_grant");
             res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code not found");
+            audit.error(Errors.INVALID_CODE);
             return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
                     .build();
         }
+
+        audit.user(accessCode.getUser());
+
+        ClientModel client = authorizeClient(authorizationHeader, formData, audit);
+
         if (accessCode.isExpired()) {
             Map<String, String> res = new HashMap<String, String>();
             res.put(OAuth2Constants.ERROR, "invalid_grant");
             res.put(OAuth2Constants.ERROR_DESCRIPTION, "Code is expired");
+            audit.error(Errors.INVALID_CODE);
             return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
                     .build();
         }
@@ -414,6 +502,7 @@ public class TokenService {
             Map<String, String> res = new HashMap<String, String>();
             res.put(OAuth2Constants.ERROR, "invalid_grant");
             res.put(OAuth2Constants.ERROR_DESCRIPTION, "Token expired");
+            audit.error(Errors.INVALID_CODE);
             return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
                     .build();
         }
@@ -421,19 +510,24 @@ public class TokenService {
             Map<String, String> res = new HashMap<String, String>();
             res.put(OAuth2Constants.ERROR, "invalid_grant");
             res.put(OAuth2Constants.ERROR_DESCRIPTION, "Auth error");
+            audit.error(Errors.INVALID_CODE);
             return Response.status(Response.Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).entity(res)
                     .build();
         }
+
         logger.debug("accessRequest SUCCESS");
-        AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
+
+        AccessTokenResponse res = tokenManager.responseBuilder(realm, client, audit)
                 .accessToken(accessCode.getToken())
                 .generateIDToken()
                 .generateRefreshToken().build();
 
+        audit.success();
+
         return Cors.add(request, Response.ok(res)).auth().allowedOrigins(client).allowedMethods("POST").build();
     }
 
-    protected ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData) {
+    protected ClientModel authorizeClient(String authorizationHeader, MultivaluedMap<String, String> formData, Audit audit) {
         String client_id = null;
         String clientSecret = null;
         if (authorizationHeader != null) {
@@ -456,11 +550,14 @@ public class TokenService {
             throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
         }
 
+        audit.client(client_id);
+
         ClientModel client = realm.findClient(client_id);
         if (client == null) {
             Map<String, String> error = new HashMap<String, String>();
             error.put(OAuth2Constants.ERROR, "invalid_client");
             error.put(OAuth2Constants.ERROR_DESCRIPTION, "Could not find client");
+            audit.error(Errors.CLIENT_NOT_FOUND);
             throw new BadRequestException("Could not find client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
         }
 
@@ -468,6 +565,7 @@ public class TokenService {
             Map<String, String> error = new HashMap<String, String>();
             error.put(OAuth2Constants.ERROR, "invalid_client");
             error.put(OAuth2Constants.ERROR_DESCRIPTION, "Client is not enabled");
+            audit.error(Errors.CLIENT_DISABLED);
             throw new BadRequestException("Client is not enabled", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
         }
 
@@ -475,6 +573,7 @@ public class TokenService {
             if (!client.validateSecret(clientSecret)) {
                 Map<String, String> error = new HashMap<String, String>();
                 error.put(OAuth2Constants.ERROR, "unauthorized_client");
+                audit.error(Errors.INVALID_CLIENT_CREDENTIALS);
                 throw new BadRequestException("Unauthorized Client", Response.status(Response.Status.BAD_REQUEST).entity(error).type("application/json").build());
             }
         }
@@ -487,6 +586,9 @@ public class TokenService {
                               @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
                               final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state, final @QueryParam("prompt") String prompt) {
         logger.info("TokenService.loginPage");
+
+        audit.event(Events.LOGIN).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
+
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
         if (!checkSsl()) {
@@ -495,20 +597,24 @@ public class TokenService {
 
         if (!realm.isEnabled()) {
             logger.warn("Realm not enabled");
+            audit.error(Errors.REALM_DISABLED);
             return oauth.forwardToSecurityFailure("Realm not enabled");
         }
         ClientModel client = realm.findClient(clientId);
         if (client == null) {
             logger.warn("Unknown login requester: " + clientId);
+            audit.error(Errors.CLIENT_NOT_FOUND);
             return oauth.forwardToSecurityFailure("Unknown login requester.");
         }
 
         if (!client.isEnabled()) {
             logger.warn("Login requester not enabled.");
+            audit.error(Errors.CLIENT_DISABLED);
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
         redirect = verifyRedirectUri(redirect, client);
         if (redirect == null) {
+            audit.error(Errors.INVALID_REDIRECT_URI);
             return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
         }
 
@@ -516,7 +622,8 @@ public class TokenService {
         UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
         if (user != null) {
             logger.debug(user.getLoginName() + " already logged in.");
-            return oauth.processAccessCode(scopeParam, state, redirect, client, user);
+            audit.user(user).detail(Details.AUTH_METHOD, "sso");
+            return oauth.processAccessCode(scopeParam, state, redirect, client, user, null, false, "sso", audit);
         }
 
         if (prompt != null && prompt.equals("none")) {
@@ -532,6 +639,9 @@ public class TokenService {
                                  @QueryParam("redirect_uri") String redirect, final @QueryParam("client_id") String clientId,
                                  final @QueryParam("scope") String scopeParam, final @QueryParam("state") String state) {
         logger.info("**********registerPage()");
+
+        audit.event(Events.REGISTER).client(clientId).detail(Details.REDIRECT_URI, redirect).detail(Details.RESPONSE_TYPE, "code");
+
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
         if (!checkSsl()) {
@@ -540,26 +650,31 @@ public class TokenService {
 
         if (!realm.isEnabled()) {
             logger.warn("Realm not enabled");
+            audit.error(Errors.REALM_DISABLED);
             return oauth.forwardToSecurityFailure("Realm not enabled");
         }
         ClientModel client = realm.findClient(clientId);
         if (client == null) {
             logger.warn("Unknown login requester.");
+            audit.error(Errors.CLIENT_NOT_FOUND);
             return oauth.forwardToSecurityFailure("Unknown login requester.");
         }
 
         if (!client.isEnabled()) {
             logger.warn("Login requester not enabled.");
+            audit.error(Errors.CLIENT_DISABLED);
             return oauth.forwardToSecurityFailure("Login requester not enabled.");
         }
 
         redirect = verifyRedirectUri(redirect, client);
         if (redirect == null) {
+            audit.error(Errors.INVALID_REDIRECT_URI);
             return oauth.forwardToSecurityFailure("Invalid redirect_uri.");
         }
 
         if (!realm.isRegistrationAllowed()) {
             logger.warn("Registration not allowed");
+            audit.error(Errors.REGISTRATION_DISABLED);
             return oauth.forwardToSecurityFailure("Registration not allowed");
         }
 
@@ -574,6 +689,8 @@ public class TokenService {
     public Response logout(final @QueryParam("redirect_uri") String redirectUri) {
         // todo do we care if anybody can trigger this?
 
+        audit.event(Events.LOGOUT).detail(Details.REDIRECT_URI, redirectUri);
+
         // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
         UserModel user = authManager.authenticateIdentityCookie(realm, uriInfo, headers, false);
         if (user != null) {
@@ -581,6 +698,8 @@ public class TokenService {
             authManager.expireIdentityCookie(realm, uriInfo);
             authManager.expireRememberMeCookie(realm, uriInfo);
             resourceAdminManager.logoutUser(realm, user);
+
+            audit.user(user).success();
         } else {
             logger.info("No user logged in for logout");
         }
@@ -592,6 +711,8 @@ public class TokenService {
     @POST
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     public Response processOAuth(final MultivaluedMap<String, String> formData) {
+        audit.event(Events.LOGIN).detail(Details.RESPONSE_TYPE, "code");
+
         OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
 
         if (!checkSsl()) {
@@ -607,21 +728,39 @@ public class TokenService {
             logger.debug("Failed to verify signature", ignored);
         }
         if (!verifiedCode) {
+            audit.error(Errors.INVALID_CODE);
             return oauth.forwardToSecurityFailure("Illegal access code.");
         }
         String key = input.readContentAsString();
+        audit.detail(Details.CODE_ID, key);
+
         AccessCodeEntry accessCodeEntry = tokenManager.getAccessCode(key);
         if (accessCodeEntry == null) {
+            audit.error(Errors.INVALID_CODE);
             return oauth.forwardToSecurityFailure("Unknown access code.");
         }
 
         String redirect = accessCodeEntry.getRedirectUri();
         String state = accessCodeEntry.getState();
 
+        audit.client(accessCodeEntry.getClient())
+                .user(accessCodeEntry.getUser())
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, accessCodeEntry.getAuthMethod())
+                .detail(Details.REDIRECT_URI, redirect)
+                .detail(Details.USERNAME, accessCodeEntry.getUsername());
+
+        if (accessCodeEntry.isRememberMe()) {
+            audit.detail(Details.REMEMBER_ME, "true");
+        }
+
         if (formData.containsKey("cancel")) {
+            audit.error(Errors.REJECTED_BY_USER);
             return redirectAccessDenied(redirect, state);
         }
 
+        audit.success();
+
         accessCodeEntry.setExpiration(Time.currentTime() + realm.getAccessCodeLifespan());
         return oauth.redirectAccessCode(accessCodeEntry, state, redirect);
     }
diff --git a/social/core/pom.xml b/social/core/pom.xml
index e201e0c..09a4592 100755
--- a/social/core/pom.xml
+++ b/social/core/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
diff --git a/social/google/pom.xml b/social/google/pom.xml
index 273f457..925965b 100755
--- a/social/google/pom.xml
+++ b/social/google/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>

social/pom.xml 3(+2 -1)

diff --git a/social/pom.xml b/social/pom.xml
index 05b3fc1..9e04645 100755
--- a/social/pom.xml
+++ b/social/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
diff --git a/social/twitter/pom.xml b/social/twitter/pom.xml
index 985994a..eb907c6 100755
--- a/social/twitter/pom.xml
+++ b/social/twitter/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-social-parent</artifactId>
         <groupId>org.keycloak</groupId>
diff --git a/spi/authentication-model/pom.xml b/spi/authentication-model/pom.xml
index 9a30d3a..fa521a4 100644
--- a/spi/authentication-model/pom.xml
+++ b/spi/authentication-model/pom.xml
@@ -64,8 +64,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java
index 58bf423..66166ab 100644
--- a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java
@@ -9,8 +9,7 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.spi.authentication.AuthProviderStatus;
-import org.keycloak.spi.authentication.AuthResult;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
 import org.keycloak.spi.authentication.AuthenticationProvider;
 import org.keycloak.spi.authentication.AuthenticationProviderException;
 
@@ -24,22 +23,19 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
     private static final Logger logger = Logger.getLogger(AbstractModelAuthenticationProvider.class);
 
     @Override
-    public AuthResult validatePassword(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
+    public AuthUser getUser(RealmModel currentRealm, Map<String, String> config, String username) throws AuthenticationProviderException {
         RealmModel realm = getRealm(currentRealm, config);
-
         UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
+        return user == null ? null : createAuthenticatedUserInstance(user);
+    }
 
-        if (user == null) {
-            return new AuthResult(AuthProviderStatus.USER_NOT_FOUND);
-        }
+    @Override
+    public AuthProviderStatus validatePassword(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
+        RealmModel realm = getRealm(currentRealm, config);
+        UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
 
         boolean result = realm.validatePassword(user, password);
-        if (!result) {
-            return  new AuthResult(AuthProviderStatus.INVALID_CREDENTIALS);
-        }
-
-        AuthenticatedUser authUser = createAuthenticatedUserInstance(user);
-        return new AuthResult(AuthProviderStatus.SUCCESS).setProviderName(getName()).setUser(authUser);
+        return result ? AuthProviderStatus.SUCCESS : AuthProviderStatus.INVALID_CREDENTIALS;
     }
 
     @Override
@@ -54,7 +50,7 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
 
         UserModel user = realm.getUser(username);
         if (user == null) {
-            logger.debugf("User '%s' doesn't exists. Skip password update", username);
+            logger.warnf("User '%s' doesn't exists. Skip password update", username);
             return false;
         }
 
@@ -68,5 +64,9 @@ public abstract class AbstractModelAuthenticationProvider implements Authenticat
 
     protected abstract RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) throws AuthenticationProviderException;
 
-    protected abstract AuthenticatedUser createAuthenticatedUserInstance(UserModel user);
+    protected AuthUser createAuthenticatedUserInstance(UserModel user) {
+        return new AuthUser(user.getId(), user.getLoginName(), getName())
+                .setName(user.getFirstName(), user.getLastName())
+                .setEmail(user.getEmail());
+    }
 }
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java
index a24a495..a4d129b 100644
--- a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java
@@ -7,7 +7,7 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.spi.authentication.AuthProviderConstants;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
 import org.keycloak.spi.authentication.AuthenticationProviderException;
 
 /**
@@ -40,11 +40,4 @@ public class ExternalModelAuthenticationProvider extends AbstractModelAuthentica
         }
         return realm;
     }
-
-    @Override
-    protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
-        return new AuthenticatedUser(user.getId(), user.getLoginName())
-                .setName(user.getFirstName(), user.getLastName())
-                .setEmail(user.getEmail());
-    }
 }
diff --git a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java
index 5169489..29ab43a 100644
--- a/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java
+++ b/spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java
@@ -5,7 +5,7 @@ import java.util.Map;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.spi.authentication.AuthProviderConstants;
-import org.keycloak.spi.authentication.AuthenticatedUser;
+import org.keycloak.spi.authentication.AuthUser;
 
 /**
  * AbstractModelAuthenticationProvider, which uses current realm to call operations on
@@ -23,10 +23,4 @@ public class ModelAuthenticationProvider extends AbstractModelAuthenticationProv
     protected RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) {
         return currentRealm;
     }
-
-    @Override
-    protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
-        // We don't want AuthenticatedUser instance. Auto-registration won't never happen with this provider
-        return null;
-    }
 }
diff --git a/spi/authentication-picketlink/pom.xml b/spi/authentication-picketlink/pom.xml
index ce78e8d..d0b5dcf 100644
--- a/spi/authentication-picketlink/pom.xml
+++ b/spi/authentication-picketlink/pom.xml
@@ -85,8 +85,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java
index 3a4da3e..ffc1ab6 100755
--- a/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java
+++ b/spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java
@@ -1,101 +1,99 @@
-package org.keycloak.spi.authentication.picketlink;
-
-import java.util.Map;
-
-import org.jboss.logging.Logger;
-import org.jboss.resteasy.spi.ResteasyProviderFactory;
-import org.keycloak.models.RealmModel;
-import org.keycloak.spi.authentication.AuthProviderStatus;
-import org.keycloak.spi.authentication.AuthResult;
-import org.keycloak.spi.authentication.AuthProviderConstants;
-import org.keycloak.spi.authentication.AuthenticatedUser;
-import org.keycloak.spi.authentication.AuthenticationProvider;
-import org.keycloak.spi.authentication.AuthenticationProviderException;
-import org.keycloak.spi.picketlink.PartitionManagerProvider;
-import org.keycloak.util.ProviderLoader;
-import org.picketlink.idm.IdentityManager;
-import org.picketlink.idm.PartitionManager;
-import org.picketlink.idm.credential.Credentials;
-import org.picketlink.idm.credential.Password;
-import org.picketlink.idm.credential.UsernamePasswordCredentials;
-import org.picketlink.idm.model.basic.BasicModel;
-import org.picketlink.idm.model.basic.User;
-
-/**
- * AuthenticationProvider, which delegates authentication to picketlink
- *
- * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
- */
-public class PicketlinkAuthenticationProvider implements AuthenticationProvider {
-
-    private static final Logger logger = Logger.getLogger(PicketlinkAuthenticationProvider.class);
-
-    @Override
-    public String getName() {
-        return AuthProviderConstants.PROVIDER_NAME_PICKETLINK;
-    }
-
-    @Override
-    public AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
-        IdentityManager identityManager = getIdentityManager(realm);
-
-        User picketlinkUser = BasicModel.getUser(identityManager, username);
-        if (picketlinkUser == null) {
-            return new AuthResult(AuthProviderStatus.USER_NOT_FOUND);
-        }
-
-        UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
-        credential.setUsername(username);
-        credential.setPassword(new Password(password.toCharArray()));
-        identityManager.validateCredentials(credential);
-        if (credential.getStatus() == Credentials.Status.VALID) {
-            AuthResult result = new AuthResult(AuthProviderStatus.SUCCESS);
-
-            AuthenticatedUser authenticatedUser = new AuthenticatedUser(picketlinkUser.getId(), picketlinkUser.getLoginName())
-                    .setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
-                    .setEmail(picketlinkUser.getEmail());
-            result.setUser(authenticatedUser).setProviderName(getName());
-            return result;
-        } else {
-            return new AuthResult(AuthProviderStatus.INVALID_CREDENTIALS);
-        }
-    }
-
-    @Override
-    public boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
-        IdentityManager identityManager = getIdentityManager(realm);
-
-        User picketlinkUser = BasicModel.getUser(identityManager, username);
-        if (picketlinkUser == null) {
-            logger.debugf("User '%s' doesn't exists. Skip password update", username);
-            return false;
-        }
-
-        identityManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
-        return true;
-    }
-
-    public IdentityManager getIdentityManager(RealmModel realm) throws AuthenticationProviderException {
-        IdentityManager identityManager = ResteasyProviderFactory.getContextData(IdentityManager.class);
-        if (identityManager == null) {
-            Iterable<PartitionManagerProvider> providers = ProviderLoader.load(PartitionManagerProvider.class);
-
-            // TODO: Priority?
-            PartitionManager partitionManager = null;
-            for (PartitionManagerProvider provider : providers) {
-                partitionManager = provider.getPartitionManager(realm);
-                if (partitionManager != null) {
-                    break;
-                }
-            }
-
-            if (partitionManager == null) {
-                throw new AuthenticationProviderException("Not able to locate PartitionManager with any PartitionManagerProvider");
-            }
-
-            identityManager = partitionManager.createIdentityManager();
-            ResteasyProviderFactory.pushContext(IdentityManager.class, identityManager);
-        }
-        return identityManager;
-    }
-}
+package org.keycloak.spi.authentication.picketlink;
+
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.spi.ResteasyProviderFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.spi.authentication.AuthProviderStatus;
+import org.keycloak.spi.authentication.AuthProviderConstants;
+import org.keycloak.spi.authentication.AuthUser;
+import org.keycloak.spi.authentication.AuthenticationProvider;
+import org.keycloak.spi.authentication.AuthenticationProviderException;
+import org.keycloak.spi.picketlink.PartitionManagerProvider;
+import org.keycloak.util.ProviderLoader;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.UsernamePasswordCredentials;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+
+/**
+ * AuthenticationProvider, which delegates authentication to picketlink
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PicketlinkAuthenticationProvider implements AuthenticationProvider {
+
+    private static final Logger logger = Logger.getLogger(PicketlinkAuthenticationProvider.class);
+
+    @Override
+    public String getName() {
+        return AuthProviderConstants.PROVIDER_NAME_PICKETLINK;
+    }
+
+    @Override
+    public AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException {
+        IdentityManager identityManager = getIdentityManager(realm);
+        User picketlinkUser = BasicModel.getUser(identityManager, username);
+        return picketlinkUser == null ? null : new AuthUser(picketlinkUser.getId(), picketlinkUser.getLoginName(), getName())
+                .setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
+                .setEmail(picketlinkUser.getEmail())
+                .setProviderName(getName());
+    }
+
+    @Override
+    public AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
+        IdentityManager identityManager = getIdentityManager(realm);
+
+        UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
+        credential.setUsername(username);
+        credential.setPassword(new Password(password.toCharArray()));
+        identityManager.validateCredentials(credential);
+        if (credential.getStatus() == Credentials.Status.VALID) {
+            return AuthProviderStatus.SUCCESS;
+        } else {
+            return AuthProviderStatus.INVALID_CREDENTIALS;
+        }
+    }
+
+    @Override
+    public boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
+        IdentityManager identityManager = getIdentityManager(realm);
+
+        User picketlinkUser = BasicModel.getUser(identityManager, username);
+        if (picketlinkUser == null) {
+            logger.debugf("User '%s' doesn't exists. Skip password update", username);
+            return false;
+        }
+
+        identityManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
+        return true;
+    }
+
+    public IdentityManager getIdentityManager(RealmModel realm) throws AuthenticationProviderException {
+        IdentityManager identityManager = ResteasyProviderFactory.getContextData(IdentityManager.class);
+        if (identityManager == null) {
+            Iterable<PartitionManagerProvider> providers = ProviderLoader.load(PartitionManagerProvider.class);
+
+            // TODO: Priority?
+            PartitionManager partitionManager = null;
+            for (PartitionManagerProvider provider : providers) {
+                partitionManager = provider.getPartitionManager(realm);
+                if (partitionManager != null) {
+                    break;
+                }
+            }
+
+            if (partitionManager == null) {
+                throw new AuthenticationProviderException("Not able to locate PartitionManager with any PartitionManagerProvider");
+            }
+
+            identityManager = partitionManager.createIdentityManager();
+            ResteasyProviderFactory.pushContext(IdentityManager.class, identityManager);
+        }
+        return identityManager;
+    }
+}
diff --git a/spi/authentication-spi/pom.xml b/spi/authentication-spi/pom.xml
index 1c822e4..cbaee59 100644
--- a/spi/authentication-spi/pom.xml
+++ b/spi/authentication-spi/pom.xml
@@ -38,8 +38,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
         </plugins>
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
index 1ab1836..014d432 100644
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java
@@ -12,13 +12,24 @@ public interface AuthenticationProvider {
     String getName();
 
     /**
+     * Get user by given username or email. Return user instance or null if user doesn't exists in this authentication provider
+     *
+     * @param realm
+     * @param configuration
+     * @param username or email
+     * @return found user or null if user with given username doesn't exists
+     * @throws AuthenticationProviderException
+     */
+    AuthUser getUser(RealmModel realm, Map<String, String> configuration, String username) throws AuthenticationProviderException;
+
+    /**
      * Standard Authentication flow
      *
      * @param username
      * @param password
      * @return result of authentication, which might eventually encapsulate info about authenticated user and provider which successfully authenticated
      */
-    AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
+    AuthProviderStatus validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
 
 
     /**
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java
index 669153e..c6a59a1 100644
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java
@@ -1,14 +1,15 @@
 package org.keycloak.spi.authentication;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.jboss.logging.Logger;
+import org.keycloak.models.AuthenticationLinkModel;
 import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.util.ProviderLoader;
 
 /**
@@ -22,7 +23,6 @@ import org.keycloak.util.ProviderLoader;
 public class AuthenticationProviderManager {
 
     private static final Logger logger = Logger.getLogger(AuthenticationProviderManager.class);
-    private static final AuthenticationProviderModel DEFAULT_PROVIDER = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, true, Collections.EMPTY_MAP);
 
     private final RealmModel realm;
     private final Map<String, AuthenticationProvider> delegates;
@@ -47,85 +47,138 @@ public class AuthenticationProviderManager {
         this.delegates = delegates;
     }
 
-    public AuthResult validatePassword(String username, String password) {
-        List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
-        boolean userExists = false;
-
-        for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
-            String providerName = authProviderConfig.getProviderName();
-
-            AuthenticationProvider delegate = getDelegate(providerName);
+    public AuthUser getUser(String username) {
+        List<AuthenticationProviderModel> authProviderModels = getConfiguredProviderModels(realm);
+        for (AuthenticationProviderModel providerModel : authProviderModels) {
+            AuthenticationProvider delegate = getProvider(providerModel.getProviderName());
             if (delegate == null) {
                 continue;
             }
 
             try {
-                AuthResult currentResult = delegate.validatePassword(realm, authProviderConfig.getConfig(), username, password);
-                logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.getAuthProviderStatus().toString(), username);
-
-                if (currentResult.getAuthProviderStatus() == AuthProviderStatus.SUCCESS) {
-                    return currentResult;
-                } else if (currentResult.getAuthProviderStatus() == AuthProviderStatus.INVALID_CREDENTIALS) {
-                    userExists = true;
+                AuthUser authUser = delegate.getUser(realm, providerModel.getConfig(), username);
+                if (authUser != null) {
+                    logger.debugf("User '%s' found with provider '%s'", username, providerModel.getProviderName());
+                    return authUser;
                 }
             } catch (AuthenticationProviderException ape) {
                 logger.warn(ape.getMessage(), ape);
             }
         }
 
-        AuthProviderStatus status = userExists ? AuthProviderStatus.INVALID_CREDENTIALS : AuthProviderStatus.USER_NOT_FOUND;
-        logger.debugf("Not able to authenticate '%s' with any authentication provider. Status: '%s'", username, status.toString());
+        logger.debugf("User '%s' not found with any provider", username);
+        return null;
+    }
+
+    public AuthProviderStatus validatePassword(UserModel user, String password) {
+        AuthenticationLinkModel authLink = realm.getAuthenticationLink(user);
+        if (authLink == null) {
+            authLink = new AuthenticationLinkModel(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName(), user.getId());
+        }
+
+        String providerName = authLink.getAuthProvider();
 
-        return new AuthResult(status);
+        AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
+        AuthenticationProvider delegate = getProvider(providerName);
+        if (delegate == null || providerModel == null) {
+            return AuthProviderStatus.FAILED;
+        }
+
+        try {
+            checkCorrectAuthLink(delegate, providerModel, authLink, user.getLoginName());
+
+            AuthProviderStatus currentResult = delegate.validatePassword(realm, providerModel.getConfig(), user.getLoginName(), password);
+            logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.toString(), user.getLoginName());
+            return currentResult;
+        } catch (AuthenticationProviderException ape) {
+            logger.warn(ape.getMessage(), ape);
+            return AuthProviderStatus.FAILED;
+        }
     }
 
-    public void updatePassword(String username, String password) throws AuthenticationProviderException {
-        List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
+    public boolean updatePassword(UserModel user, String password) throws AuthenticationProviderException {
+        AuthenticationLinkModel authLink = realm.getAuthenticationLink(user);
+        if (authLink == null) {
+            authLink = new AuthenticationLinkModel(AuthenticationProviderModel.DEFAULT_PROVIDER.getProviderName(), user.getId());
+        }
+
+        String providerName = authLink.getAuthProvider();
 
-        for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
+        AuthenticationProviderModel providerModel = getConfiguredProviderModel(realm, providerName);
+        if (providerModel == null) {
+            return false;
+        }
+
+        String username = user.getLoginName();
 
-            // Update just those, which support password update
-            if (authProviderConfig.isPasswordUpdateSupported()) {
-                String providerName = authProviderConfig.getProviderName();
-                AuthenticationProvider delegate = getDelegate(providerName);
+        // Update just those, which support password update
+        if (providerModel.isPasswordUpdateSupported()) {
+            try {
+                AuthenticationProvider delegate = getProvider(providerName);
                 if (delegate == null) {
-                    continue;
+                    return false;
                 }
 
-                try {
-                    if (delegate.updateCredential(realm, authProviderConfig.getConfig(), username, password)) {
-                        logger.debugf("Updated password in authentication provider '%s' for user '%s'", delegate.getName(), username);
-                    } else {
-                        logger.debugf("Password not updated in authentication provider '%s' for user '%s'", delegate.getName(), username);
-                    }
-                } catch (AuthenticationProviderException ape) {
-                    // Rethrow it to upper layer
-                    logger.warn("Failed to update password: " + ape.getMessage());
-                    throw ape;
+                checkCorrectAuthLink(delegate, providerModel, authLink, username);
+
+                if (delegate.updateCredential(realm,providerModel.getConfig(), user.getLoginName(), password)) {
+                    logger.debugf("Updated password in authentication provider '%s' for user '%s'", providerName, username);
+                    return true;
+                } else {
+                    logger.warnf("Password not updated in authentication provider '%s' for user '%s'", providerName, username);
+                    return false;
                 }
-            } else {
-                logger.debugf("Skip password update for authentication provider '%s' for user '%s'", authProviderConfig.getProviderName(), username);
+            } catch (AuthenticationProviderException ape) {
+                // Rethrow it to upper layer
+                logger.warn("Failed to update password: " + ape.getMessage());
+                throw ape;
             }
+        } else {
+            logger.warnf("Skip password update for authentication provider '%s' for user '%s'", providerName, username);
+            return false;
         }
     }
 
-    private AuthenticationProvider getDelegate(String providerName) {
+    private AuthenticationProvider getProvider(String providerName) {
         AuthenticationProvider delegate = delegates.get(providerName);
         if (delegate == null) {
-            logger.warnf("Configured provider with name '%s' not found", providerName);
+            logger.warnf("Provider '%s' not available on classpath", providerName);
         }
         return delegate;
     }
 
-    private List<AuthenticationProviderModel> getConfiguredProviders(RealmModel realm) {
+    private List<AuthenticationProviderModel> getConfiguredProviderModels(RealmModel realm) {
         List<AuthenticationProviderModel> configuredProviders = realm.getAuthenticationProviders();
 
         // Use model based authentication of current realm by default
         if (configuredProviders == null || configuredProviders.isEmpty()) {
-            configuredProviders = new ArrayList<AuthenticationProviderModel>();
-            configuredProviders.add(DEFAULT_PROVIDER);
+            configuredProviders = Collections.EMPTY_LIST;
+            logger.warnf("No authentication providers found");
         }
 
         return configuredProviders;
     }
+
+    private AuthenticationProviderModel getConfiguredProviderModel(RealmModel realm, String providerName) {
+        List<AuthenticationProviderModel> providers = getConfiguredProviderModels(realm);
+        for (AuthenticationProviderModel provider : providers) {
+            if (providerName.equals(provider.getProviderName())) {
+                return provider;
+            }
+        }
+
+        logger.warnf("Provider '%s' not configured in realm", providerName);
+        return null;
+    }
+
+    // Check if ID of linked AuthUser is same as expected ID from authenticationLink . It should catch the case when for example user "john" was deleted in LDAP
+    // and then user "john" has been created again, but it's actually different user with different ID
+    private void checkCorrectAuthLink(AuthenticationProvider authProvider, AuthenticationProviderModel providerModel,
+                                      AuthenticationLinkModel authLinkModel, String username) throws AuthenticationProviderException {
+        AuthUser authUser = authProvider.getUser(realm, providerModel.getConfig(), username);
+        String userExternalId = authUser.getId();
+        if (!userExternalId.equals(authLinkModel.getAuthUserId())) {
+            throw new AuthenticationProviderException("ID did not match! ID from provider: " + userExternalId + ", ID from authentication link: " + authLinkModel.getAuthUserId());
+        }
+    }
 }
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderConstants.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderConstants.java
index cae2cb6..d736da7 100644
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderConstants.java
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderConstants.java
@@ -5,12 +5,11 @@ package org.keycloak.spi.authentication;
  */
 public class AuthProviderConstants {
 
+    // Model is default provider. See AuthenticationProviderModel.DEFAULT_PROVIDER
     public static final String PROVIDER_NAME_MODEL = "model";
     public static final String PROVIDER_NAME_EXTERNAL_MODEL = "externalModel";
     public static final String PROVIDER_NAME_PICKETLINK = "picketlink";
 
-    public static final String DEFAULT_PROVIDER = PROVIDER_NAME_MODEL;
-
     // Used in external-model provider
     public static final String EXTERNAL_REALM_ID = "externalRealmId";
 }
diff --git a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java
index 56d05d0..c782dfc 100644
--- a/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java
+++ b/spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java
@@ -7,6 +7,6 @@ package org.keycloak.spi.authentication;
  */
 public enum AuthProviderStatus {
 
-    SUCCESS, INVALID_CREDENTIALS, USER_NOT_FOUND
+    SUCCESS, INVALID_CREDENTIALS, FAILED
 
 }
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index e784d04..115c618 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <parent>
         <artifactId>keycloak-parent</artifactId>
         <groupId>org.keycloak</groupId>
@@ -38,6 +39,16 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-audit-api</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-admin-ui-styles</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -321,8 +332,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
             <plugin>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/ProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
index d153a11..59cbd00 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
@@ -36,7 +36,6 @@ import java.io.IOException;
 import java.net.URI;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 /**
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index 7909d83..db4c3ea 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -22,13 +22,17 @@
 package org.keycloak.testsuite.actions;
 
 import org.junit.Assert;
+import org.junit.Before;
 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.models.RealmModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -53,17 +57,10 @@ import java.util.regex.Pattern;
 public class RequiredActionEmailVerificationTest {
 
     @ClassRule
-    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
+    public static KeycloakRule keycloakRule = new KeycloakRule();
 
-        @Override
-        public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
-            appRealm.setVerifyEmail(true);
-
-            UserModel user = appRealm.getUser("test-user@localhost");
-            user.addRequiredAction(RequiredAction.VERIFY_EMAIL);
-        }
-
-    });
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
 
     @Rule
     public WebRule webRule = new WebRule(this);
@@ -75,6 +72,9 @@ public class RequiredActionEmailVerificationTest {
     protected WebDriver driver;
 
     @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
     protected AppPage appPage;
 
     @WebResource
@@ -86,6 +86,21 @@ public class RequiredActionEmailVerificationTest {
     @WebResource
     protected RegisterPage registerPage;
 
+    @Before
+    public void before() {
+        keycloakRule.configure(new KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
+                appRealm.setVerifyEmail(true);
+
+                UserModel user = appRealm.getUser("test-user@localhost");
+                user.setEmailVerified(false);
+            }
+
+        });
+    }
+
     @Test
     public void verifyEmailExisting() throws IOException, MessagingException {
         loginPage.open();
@@ -105,9 +120,19 @@ public class RequiredActionEmailVerificationTest {
 
         String verificationUrl = m.group(1);
 
+        Event sendEvent = events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent();
+
+        String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
+
+        Assert.assertEquals(mailCodeId, verificationUrl.split("key=")[1]);
+
         driver.navigate().to(verificationUrl.trim());
 
+        events.expectRequiredAction("verify_email").detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
+
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().detail(Details.CODE_ID, mailCodeId).assertEvent();
     }
 
     @Test
@@ -116,6 +141,8 @@ public class RequiredActionEmailVerificationTest {
         loginPage.clickRegister();
         registerPage.register("firstName", "lastName", "email", "verifyEmail", "password", "password");
 
+        String userId = events.expectRegister("verifyEmail", "email").assertEvent().getUserId();
+
         Assert.assertTrue(verifyEmailPage.isCurrent());
 
         Assert.assertEquals(1, greenMail.getReceivedMessages().length);
@@ -128,23 +155,34 @@ public class RequiredActionEmailVerificationTest {
         Matcher m = p.matcher(body);
         m.matches();
 
+        Event sendEvent = events.expectRequiredAction("send_verify_email").user(userId).detail("username", "verifyEmail").detail("email", "email").assertEvent();
+
+        String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
+
         String verificationUrl = m.group(1);
 
         driver.navigate().to(verificationUrl.trim());
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectRequiredAction("verify_email").user(userId).detail("username", "verifyEmail").detail("email", "email").detail(Details.CODE_ID, mailCodeId).assertEvent();
+
+        events.expectLogin().user(userId).detail("username", "verifyEmail").detail(Details.CODE_ID, mailCodeId).assertEvent();
     }
 
     @Test
     public void verifyEmailResend() throws IOException, MessagingException {
         loginPage.open();
-        loginPage.clickRegister();
-        registerPage.register("firstName2", "lastName2", "email2", "verifyEmail2", "password2", "password2");
+        loginPage.login("test-user@localhost", "password");
 
         Assert.assertTrue(verifyEmailPage.isCurrent());
 
         Assert.assertEquals(1, greenMail.getReceivedMessages().length);
 
+        Event sendEvent = events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent();
+
+        String mailCodeId = sendEvent.getDetails().get(Details.CODE_ID);
+
         verifyEmailPage.clickResendEmail();
 
         Assert.assertEquals(2, greenMail.getReceivedMessages().length);
@@ -157,11 +195,17 @@ public class RequiredActionEmailVerificationTest {
         Matcher m = p.matcher(body);
         m.matches();
 
+        events.expectRequiredAction("send_verify_email").detail("email", "test-user@localhost").assertEvent(sendEvent);
+
         String verificationUrl = m.group(1);
 
         driver.navigate().to(verificationUrl.trim());
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectRequiredAction("verify_email").detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
+
+        events.expectLogin().assertEvent();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
index 917d047..c2044ef 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
@@ -25,10 +25,13 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.audit.Details;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -60,10 +63,16 @@ public class RequiredActionMultipleActionsTest {
     @Rule
     public WebRule webRule = new WebRule(this);
 
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
     @WebResource
     protected WebDriver driver;
 
     @WebResource
+    protected OAuthClient oauth;
+
+    @WebResource
     protected AppPage appPage;
 
     @WebResource
@@ -95,14 +104,21 @@ public class RequiredActionMultipleActionsTest {
         }
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
     }
 
     public void updatePassword() {
         changePasswordPage.changePassword("new-password", "new-password");
+
+        events.expectRequiredAction("update_password").assertEvent();
     }
 
     public void updateProfile() {
         updateProfilePage.update("New first", "New last", "new@email.com");
+
+        events.expectRequiredAction("update_profile").assertEvent();
+        events.expectRequiredAction("update_email").detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
index 28197d0..00fe91e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java
@@ -29,6 +29,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
@@ -63,6 +64,9 @@ public class RequiredActionResetPasswordTest {
     public WebRule webRule = new WebRule(this);
 
     @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
     public GreenMailRule greenMail = new GreenMailRule();
 
     @WebResource
@@ -88,12 +92,20 @@ public class RequiredActionResetPasswordTest {
         changePasswordPage.assertCurrent();
         changePasswordPage.changePassword("new-password", "new-password");
 
+        events.expectRequiredAction("update_password").assertEvent();
+
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
+        events.expectLogin().assertEvent();
+
         oauth.openLogout();
 
+        events.expectLogout().assertEvent();
+
         loginPage.open();
         loginPage.login("test-user@localhost", "new-password");
+
+        events.expectLogin().assertEvent();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index bd5d700..d9e13c9 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -25,10 +25,12 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.audit.Details;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.utils.TimeBasedOTP;
 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.AccountTotpPage;
 import org.keycloak.testsuite.pages.AppPage;
@@ -60,6 +62,9 @@ public class RequiredActionTotpSetupTest {
     });
 
     @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
     public WebRule webRule = new WebRule(this);
 
     @WebResource
@@ -94,11 +99,17 @@ public class RequiredActionTotpSetupTest {
         loginPage.clickRegister();
         registerPage.register("firstName", "lastName", "email", "setupTotp", "password", "password");
 
+        String userId = events.expectRegister("setupTotp", "email").assertEvent().getUserId();
+
         totpPage.assertCurrent();
 
         totpPage.configure(totp.generate(totpPage.getTotpSecret()));
 
+        events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp").assertEvent();
+
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp").assertEvent();
     }
 
     @Test
@@ -112,15 +123,23 @@ public class RequiredActionTotpSetupTest {
 
         totpPage.configure(totp.generate(totpSecret));
 
+        events.expectRequiredAction("update_totp").assertEvent();
+
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
+        events.expectLogin().assertEvent();
+
         oauth.openLogout();
 
+        events.expectLogout().assertEvent();
+
         loginPage.open();
         loginPage.login("test-user@localhost", "password");
         loginTotpPage.login(totp.generate(totpSecret));
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
     }
 
     @Test
@@ -130,6 +149,8 @@ public class RequiredActionTotpSetupTest {
         loginPage.clickRegister();
         registerPage.register("firstName2", "lastName2", "email2", "setupTotp2", "password2", "password2");
 
+        String userId = events.expectRegister("setupTotp2", "email2").assertEvent().getUserId();
+
         // Configure totp
         totpPage.assertCurrent();
 
@@ -139,8 +160,13 @@ public class RequiredActionTotpSetupTest {
         // After totp config, user should be on the app page
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
+        events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
+
         // Logout
         oauth.openLogout();
+        events.expectLogout().user(userId).assertEvent();
 
         // Try to login after logout
         loginPage.open();
@@ -153,15 +179,24 @@ public class RequiredActionTotpSetupTest {
         // Login with one-time password
         loginTotpPage.login(totp.generate(totpCode));
 
+        events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
+
         // Open account page
         accountTotpPage.open();
         accountTotpPage.assertCurrent();
 
+        events.expectLogin().user(userId).detail(Details.AUTH_METHOD, "sso").client("account")
+                .detail(Details.REDIRECT_URI, "http://localhost:8081/auth/rest/realms/test/account/login-redirect?path=totp")
+                .removeDetail(Details.USERNAME).assertEvent();
+
         // Remove google authentificator
         accountTotpPage.removeTotp();
 
+        events.expectAccount("remove_totp").user(userId).assertEvent();
+
         // Logout
         oauth.openLogout();
+        events.expectLogout().user(userId).assertEvent();
 
         // Try to login
         loginPage.open();
@@ -171,7 +206,11 @@ public class RequiredActionTotpSetupTest {
         totpPage.assertCurrent();
         totpPage.configure(totp.generate(totpPage.getTotpSecret()));
 
+        events.expectRequiredAction("update_totp").user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
+
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
+        events.expectLogin().user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent();
     }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
index 0b66340..3c317b1 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
@@ -22,18 +22,20 @@
 package org.keycloak.testsuite.actions;
 
 import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.audit.Details;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
-import org.keycloak.models.UserModel.RequiredAction;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
 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;
@@ -43,20 +45,15 @@ import org.openqa.selenium.WebDriver;
  */
 public class RequiredActionUpdateProfileTest {
 
-    @Rule
-    public KeycloakRule keycloakRule = new KeycloakRule(new KeycloakSetup() {
-
-        @Override
-        public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
-            UserModel user = appRealm.getUser("test-user@localhost");
-            user.addRequiredAction(RequiredAction.UPDATE_PROFILE);
-        }
-
-    });
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule();
 
     @Rule
     public WebRule webRule = new WebRule(this);
 
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
     @WebResource
     protected WebDriver driver;
 
@@ -69,6 +66,17 @@ public class RequiredActionUpdateProfileTest {
     @WebResource
     protected LoginUpdateProfilePage updateProfilePage;
 
+    @Before
+    public void before() {
+        keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
+            @Override
+            public void config(RealmManager manager, RealmModel defaultRealm, RealmModel appRealm) {
+                UserModel user = appRealm.getUser("test-user@localhost");
+                user.addRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE);
+            }
+        });
+    }
+
     @Test
     public void updateProfile() {
         loginPage.open();
@@ -79,7 +87,12 @@ public class RequiredActionUpdateProfileTest {
 
         updateProfilePage.update("New first", "New last", "new@email.com");
 
+        events.expectRequiredAction("update_profile").assertEvent();
+        events.expectRequiredAction("update_email").detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
+
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
     }
 
     @Test
@@ -95,6 +108,8 @@ public class RequiredActionUpdateProfileTest {
         updateProfilePage.assertCurrent();
 
         Assert.assertEquals("Please specify first name", updateProfilePage.getError());
+
+        events.assertEmpty();
     }
 
     @Test
@@ -110,6 +125,8 @@ public class RequiredActionUpdateProfileTest {
         updateProfilePage.assertCurrent();
 
         Assert.assertEquals("Please specify last name", updateProfilePage.getError());
+
+        events.assertEmpty();
     }
 
     @Test
@@ -125,7 +142,8 @@ public class RequiredActionUpdateProfileTest {
         updateProfilePage.assertCurrent();
 
         Assert.assertEquals("Please specify email", updateProfilePage.getError());
-    }
 
+        events.assertEmpty();
+    }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java
index 8d910af..ab6e3b3 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/ApplicationServlet.java
@@ -21,18 +21,12 @@
  */
 package org.keycloak.testsuite;
 
-import org.apache.http.NameValuePair;
-import org.apache.http.client.utils.URLEncodedUtils;
-
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.List;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
new file mode 100644
index 0000000..5a582e9
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -0,0 +1,328 @@
+package org.keycloak.testsuite;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.jboss.logging.Logger;
+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;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.rule.KeycloakRule;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+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";
+    public static String DEFAULT_REALM = "test";
+    public static String DEFAULT_USERNAME = "test-user@localhost";
+
+    private KeycloakRule keycloak;
+
+    private static BlockingQueue<Event> events = new LinkedBlockingQueue<Event>();
+
+    public AssertEvents() {
+    }
+
+    public AssertEvents(KeycloakRule keycloak) {
+        this.keycloak = keycloak;
+    }
+
+    @Override
+    public String getId() {
+        return "assert-events";
+    }
+
+    @Override
+    public Statement apply(final Statement base, org.junit.runner.Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                events.clear();
+
+                keycloak.configure(new KeycloakRule.KeycloakSetup() {
+                    @Override
+                    public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                        Set<String> listeners = new HashSet<String>();
+                        listeners.add("jboss-logging");
+                        listeners.add("assert-events");
+                        appRealm.setAuditListeners(listeners);
+                    }
+                });
+
+                try {
+                    base.evaluate();
+
+                    Event event = events.peek();
+                    if (event != null) {
+                        Assert.fail("Unexpected event after test: " + event.getEvent());
+                    }
+                } finally {
+                    keycloak.configure(new KeycloakRule.KeycloakSetup() {
+                        @Override
+                        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                            appRealm.setAuditListeners(null);
+                        }
+                    });
+                }
+            }
+        };
+    }
+
+    public void assertEmpty() {
+         Assert.assertTrue(events.isEmpty());
+    }
+
+    public Event poll() {
+        try {
+            return events.poll(10, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            return null;
+        }
+    }
+
+    public void clear() {
+        events.clear();
+    }
+
+    public ExpectedEvent expectRequiredAction(String event) {
+        return expectLogin().event(event);
+    }
+
+    public ExpectedEvent expectLogin() {
+        return expect("login")
+                .detail(Details.CODE_ID, isCodeId())
+                .detail(Details.USERNAME, DEFAULT_USERNAME)
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.AUTH_METHOD, "form")
+                .detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
+    }
+
+    public ExpectedEvent expectCodeToToken(String codeId) {
+        return expect("code_to_token")
+                .detail(Details.CODE_ID, codeId)
+                .detail(Details.TOKEN_ID, isUUID())
+                .detail(Details.REFRESH_TOKEN_ID, isUUID());
+    }
+
+    public ExpectedEvent expectRefresh(String refreshTokenId) {
+        return expect("refresh_token")
+                .detail(Details.TOKEN_ID, isUUID())
+                .detail(Details.REFRESH_TOKEN_ID, refreshTokenId)
+                .detail(Details.UPDATED_REFRESH_TOKEN_ID, isUUID());
+    }
+
+    public ExpectedEvent expectLogout() {
+        return expect("logout").client((String) null)
+                .detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
+    }
+
+    public ExpectedEvent expectRegister(String username, String email) {
+        UserRepresentation user = keycloak.getUser("test", username);
+        return expect("register")
+                .user(user != null ? user.getId() : null)
+                .detail(Details.USERNAME, username)
+                .detail(Details.EMAIL, email)
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.REGISTER_METHOD, "form")
+                .detail(Details.REDIRECT_URI, DEFAULT_REDIRECT_URI);
+    }
+
+    public ExpectedEvent expectAccount(String event) {
+        return expect(event).client("account");
+    }
+
+    public ExpectedEvent expect(String event) {
+        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;
+        private HashMap<String, Matcher<String>> details;
+
+        public ExpectedEvent realm(RealmModel realm) {
+            expected.setRealmId(realm.getId());
+            return this;
+        }
+
+        public ExpectedEvent realm(String realmId) {
+            expected.setRealmId(realmId);
+            return this;
+        }
+
+        public ExpectedEvent client(ClientModel client) {
+            expected.setClientId(client.getClientId());
+            return this;
+        }
+
+        public ExpectedEvent client(String clientId) {
+            expected.setClientId(clientId);
+            return this;
+        }
+
+        public ExpectedEvent user(UserModel user) {
+            return user(CoreMatchers.equalTo(user.getId()));
+        }
+
+        public ExpectedEvent user(String userId) {
+            return user(CoreMatchers.equalTo(userId));
+        }
+
+        public ExpectedEvent user(Matcher<String> userId) {
+            this.userId = userId;
+            return this;
+        }
+
+        public ExpectedEvent ipAddress(String ipAddress) {
+            expected.setIpAddress(ipAddress);
+            return this;
+        }
+
+        public ExpectedEvent event(String e) {
+            expected.setEvent(e);
+            return this;
+        }
+
+        public ExpectedEvent detail(String key, String value) {
+            return detail(key, CoreMatchers.equalTo(value));
+        }
+
+        public ExpectedEvent detail(String key, Matcher<String> matcher) {
+            if (details == null) {
+                details = new HashMap<String, Matcher<String>>();
+            }
+            details.put(key, matcher);
+            return this;
+        }
+
+        public ExpectedEvent removeDetail(String key) {
+            if (details != null) {
+                details.remove(key);
+            }
+            return this;
+        }
+
+        public ExpectedEvent error(String error) {
+            expected.setError(error);
+            return this;
+        }
+
+        public Event assertEvent() {
+            try {
+                return assertEvent(events.poll(10, TimeUnit.SECONDS));
+            } catch (InterruptedException e) {
+                throw new AssertionError("No event received within timeout");
+            }
+        }
+
+        public Event assertEvent(Event actual) {
+            Assert.assertEquals(expected.getEvent(), actual.getEvent());
+            Assert.assertEquals(expected.getRealmId(), actual.getRealmId());
+            Assert.assertEquals(expected.getClientId(), actual.getClientId());
+            Assert.assertEquals(expected.getError(), actual.getError());
+            Assert.assertEquals(expected.getIpAddress(), actual.getIpAddress());
+            Assert.assertThat(actual.getUserId(), userId);
+
+            if (details == null) {
+                Assert.assertNull(actual.getDetails());
+            } else {
+                Assert.assertNotNull(actual.getDetails());
+                for (Map.Entry<String, Matcher<String>> d : details.entrySet()) {
+                    String actualValue = actual.getDetails().get(d.getKey());
+                    if (!actual.getDetails().containsKey(d.getKey())) {
+                        Assert.fail(d.getKey() + " missing");
+                    }
+
+                    if (!d.getValue().matches(actualValue)) {
+                        Assert.fail(d.getKey() + " doesn't match");
+                    }
+                }
+
+                for (String k : actual.getDetails().keySet()) {
+                    if (!details.containsKey(k)) {
+                        Assert.fail(k + " was not expected");
+                    }
+                }
+            }
+
+            return actual;
+        }
+    }
+
+    public static Matcher<String> isCodeId() {
+        return new TypeSafeMatcher<String>() {
+            @Override
+            protected boolean matchesSafely(String item) {
+                return (UUID.randomUUID().toString() + System.currentTimeMillis()).length() == item.length();
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Not an Code ID");
+            }
+        };
+    }
+
+    public static Matcher<String> isUUID() {
+        return new TypeSafeMatcher<String>() {
+            @Override
+            protected boolean matchesSafely(String item) {
+                return KeycloakModelUtils.generateId().length() == item.length();
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Not an UUID");
+            }
+        };
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
index 3ddf565..c971525 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java
@@ -27,6 +27,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationProviderModel;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
@@ -44,6 +45,7 @@ import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
 import java.security.PublicKey;
+import java.util.Arrays;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -66,6 +68,7 @@ public class CompositeRoleTest {
             realm.setSslNotRequired(true);
             realm.setEnabled(true);
             realm.addRequiredCredential(UserCredentialModel.PASSWORD);
+            realm.setAuthenticationProviders(Arrays.asList(AuthenticationProviderModel.DEFAULT_PROVIDER));
             final RoleModel realmRole1 = realm.addRole("REALM_ROLE_1");
             final RoleModel realmRole2 = realm.addRole("REALM_ROLE_2");
             final RoleModel realmRole3 = realm.addRole("REALM_ROLE_3");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java
index 7ddf7ef..fe2745c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/DummySocialServlet.java
@@ -11,7 +11,6 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.URI;
-import java.nio.charset.Charset;
 import java.util.List;
 import java.util.UUID;
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
index f952046..37cde85 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java
@@ -1,10 +1,5 @@
 package org.keycloak.testsuite.forms;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
 import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.FixMethodOrder;
@@ -34,6 +29,11 @@ import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  */
@@ -47,7 +47,7 @@ public class AuthProvidersIntegrationTest {
         @Override
         public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
             addUser(appRealm, "mary", "mary@test.com", "password-app");
-            addUser(adminstrationRealm, "mary", "mary@admin.com", "password-admin");
+            addUser(adminstrationRealm, "mary-admin", "mary@admin.com", "password-admin");
 
             AuthenticationProviderModel modelProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, false, Collections.EMPTY_MAP);
             AuthenticationProviderModel picketlinkProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
@@ -116,7 +116,7 @@ public class AuthProvidersIntegrationTest {
     @Test
     public void loginExternalModel() {
         loginPage.open();
-        loginPage.login("mary", "password-admin");
+        loginPage.login("mary-admin", "password-admin");
 
         Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
@@ -148,18 +148,18 @@ public class AuthProvidersIntegrationTest {
 
         try {
             changePasswordPage.open();
-            loginPage.login("mary", "password-admin");
+            loginPage.login("mary-admin", "password-admin");
 
             // Can't update to "pass" due to passwordPolicy
             changePasswordPage.changePassword("password-admin", "pass", "pass");
             Assert.assertEquals("Invalid password: minimum length 6", profilePage.getError());
 
-            changePasswordPage.changePassword("password-app", "password-updated", "password-updated");
+            changePasswordPage.changePassword("password-admin", "password-updated", "password-updated");
             Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
             changePasswordPage.logout();
 
             loginPage.open();
-            loginPage.login("mary", "password-updated");
+            loginPage.login("mary-admin", "password-updated");
             Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
         } finally {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index c46a13a..b4dc34c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -26,11 +26,13 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.audit.Details;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 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.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
@@ -53,6 +55,8 @@ public class LoginTest {
             user.setEmail("login@test.com");
             user.setEnabled(true);
 
+            userId = user.getId();
+
             UserCredentialModel creds = new UserCredentialModel();
             creds.setType(CredentialRepresentation.PASSWORD);
             creds.setValue("password");
@@ -62,6 +66,9 @@ public class LoginTest {
     });
 
     @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
     public WebRule webRule = new WebRule(this);
 
     @WebResource
@@ -76,6 +83,8 @@ public class LoginTest {
     @WebResource
     protected LoginPage loginPage;
 
+    private static String userId;
+
     @Test
     public void loginInvalidPassword() {
         loginPage.open();
@@ -84,6 +93,8 @@ public class LoginTest {
         loginPage.assertCurrent();
 
         Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        events.expectLogin().user((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").removeDetail(Details.CODE_ID).assertEvent();
     }
 
     @Test
@@ -94,6 +105,8 @@ public class LoginTest {
         loginPage.assertCurrent();
 
         Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        events.expectLogin().user((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").removeDetail(Details.CODE_ID).assertEvent();
     }
 
     @Test
@@ -103,6 +116,8 @@ public class LoginTest {
         
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
     }
 
     @Test
@@ -112,6 +127,8 @@ public class LoginTest {
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
     }
 
     @Test
@@ -120,8 +137,9 @@ public class LoginTest {
         loginPage.cancel();
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-
         Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+
+        events.expectLogin().error("rejected_by_user").user((String) null).removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
index a3ea179..783f07a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTotpTest.java
@@ -26,12 +26,14 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.audit.Details;
 import org.keycloak.models.RealmModel;
 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.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -44,6 +46,7 @@ import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
 import java.net.MalformedURLException;
+import java.util.Collections;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -63,11 +66,15 @@ public class LoginTotpTest {
             appRealm.updateCredential(user, credentials);
 
             user.setTotp(true);
+            appRealm.setAuditListeners(Collections.singleton("dummy"));
         }
 
     });
 
     @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
     public WebRule webRule = new WebRule(this);
 
     @Rule
@@ -83,7 +90,7 @@ public class LoginTotpTest {
     protected LoginPage loginPage;
 
     @WebResource
-    private LoginTotpPage loginTotpPage;
+    protected LoginTotpPage loginTotpPage;
 
     private TimeBasedOTP totp = new TimeBasedOTP();
 
@@ -103,6 +110,8 @@ public class LoginTotpTest {
 
         loginPage.assertCurrent();
         Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+        events.expectLogin().error("invalid_user_credentials").removeDetail(Details.CODE_ID).user((String) null).assertEvent();
     }
 
     @Test
@@ -115,6 +124,8 @@ public class LoginTotpTest {
         loginTotpPage.login(totp.generate("totpSecret"));
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
index cd7795e..aeea462 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
@@ -25,9 +25,12 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.audit.Details;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
+import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -46,6 +49,9 @@ public class RegisterTest {
     public static KeycloakRule keycloakRule = new KeycloakRule();
 
     @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
     public WebRule webRule = new WebRule(this);
 
     @WebResource
@@ -60,6 +66,9 @@ public class RegisterTest {
     @WebResource
     protected RegisterPage registerPage;
 
+    @WebResource
+    protected OAuthClient oauth;
+
     @Test
     public void registerExistingUser() {
         loginPage.open();
@@ -70,6 +79,8 @@ public class RegisterTest {
 
         registerPage.assertCurrent();
         Assert.assertEquals("Username already exists", registerPage.getError());
+
+        events.expectRegister("test-user@localhost", "email").user((String) null).error("username_in_use").assertEvent();
     }
 
     @Test
@@ -82,6 +93,8 @@ public class RegisterTest {
 
         registerPage.assertCurrent();
         Assert.assertEquals("Password confirmation doesn't match", registerPage.getError());
+
+        events.expectRegister("registerUserInvalidPasswordConfirm", "email").user((String) null).error("invalid_registration").assertEvent();
     }
 
     @Test
@@ -94,6 +107,8 @@ public class RegisterTest {
 
         registerPage.assertCurrent();
         Assert.assertEquals("Please specify password.", registerPage.getError());
+
+        events.expectRegister("registerUserMissingPassword", "email").user((String) null).error("invalid_registration").assertEvent();
     }
 
     @Test
@@ -115,8 +130,14 @@ public class RegisterTest {
             registerPage.assertCurrent();
             Assert.assertEquals("Invalid password: minimum length 8", registerPage.getError());
 
+            events.expectRegister("registerPasswordPolicy", "email").user((String) null).error("invalid_registration").assertEvent();
+
             registerPage.register("firstName", "lastName", "email", "registerPasswordPolicy", "password", "password");
             Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+            String userId = events.expectRegister("registerPasswordPolicy", "email").assertEvent().getUserId();
+
+            events.expectLogin().user(userId).detail(Details.USERNAME, "registerPasswordPolicy").assertEvent();
         } finally {
             keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -137,6 +158,8 @@ public class RegisterTest {
 
         registerPage.assertCurrent();
         Assert.assertEquals("Please specify username", registerPage.getError());
+
+        events.expectRegister(null, "email").removeDetail("username").error("invalid_registration").assertEvent();
     }
 
     @Test
@@ -148,6 +171,9 @@ public class RegisterTest {
         registerPage.register("firstName", "lastName", "email", "registerUserSuccess", "password", "password");
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        String userId = events.expectRegister("registerUserSuccess", "email").assertEvent().getUserId();
+        events.expectLogin().detail("username", "registerUserSuccess").user(userId).assertEvent();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 96f5428..0863c1a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -25,12 +25,14 @@ import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.keycloak.audit.Details;
 import org.keycloak.models.PasswordPolicy;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserCredentialModel;
 import org.keycloak.models.UserModel;
 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.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
@@ -46,6 +48,7 @@ import org.openqa.selenium.WebDriver;
 import javax.mail.MessagingException;
 import javax.mail.internet.MimeMessage;
 import java.io.IOException;
+import java.util.Collections;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -60,14 +63,19 @@ public class ResetPasswordTest {
             user.setEmail("login@test.com");
             user.setEnabled(true);
 
+            userId = user.getId();
+
             UserCredentialModel creds = new UserCredentialModel();
             creds.setType(CredentialRepresentation.PASSWORD);
             creds.setValue("password");
 
             appRealm.updateCredential(user, creds);
+            appRealm.setAuditListeners(Collections.singleton("dummy"));
         }
     }));
 
+    private static String userId;
+
     @Rule
     public WebRule webRule = new WebRule(this);
 
@@ -92,54 +100,31 @@ public class ResetPasswordTest {
     @WebResource
     protected LoginPasswordUpdatePage updatePasswordPage;
 
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
     @Test
     public void resetPassword() throws IOException, MessagingException {
-        loginPage.open();
-        loginPage.resetPassword();
-
-        resetPasswordPage.assertCurrent();
-
-        resetPasswordPage.changePassword("login-test");
-
-        resetPasswordPage.assertCurrent();
-
-        Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
-
-        Assert.assertEquals(1, greenMail.getReceivedMessages().length);
-
-        MimeMessage message = greenMail.getReceivedMessages()[0];
-
-        String body = (String) message.getContent();
-        String changePasswordUrl = body.split("\n")[3];
-
-        driver.navigate().to(changePasswordUrl.trim());
-
-        updatePasswordPage.assertCurrent();
-
-        updatePasswordPage.changePassword("resetPassword", "resetPassword");
-
-        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
-
-        oauth.openLogout();
-
-        loginPage.open();
-
-        loginPage.login("login-test", "resetPassword");
-
-        Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+        resetPassword("login-test");
     }
 
     @Test
     public void resetPasswordByEmail() throws IOException, MessagingException {
+        resetPassword("login@test.com");
+    }
+
+    private void resetPassword(String username) throws IOException, MessagingException {
         loginPage.open();
         loginPage.resetPassword();
 
         resetPasswordPage.assertCurrent();
 
-        resetPasswordPage.changePassword("login@test.com");
+        resetPasswordPage.changePassword(username);
 
         resetPasswordPage.assertCurrent();
 
+        events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, username).detail(Details.EMAIL, "login@test.com").assertEvent();
+
         Assert.assertEquals("You should receive an email shortly with further instructions.", resetPasswordPage.getSuccessMessage());
 
         Assert.assertEquals(1, greenMail.getReceivedMessages().length);
@@ -155,13 +140,21 @@ public class ResetPasswordTest {
 
         updatePasswordPage.changePassword("resetPassword", "resetPassword");
 
+        events.expectRequiredAction("update_password").user(userId).detail(Details.USERNAME, username).assertEvent();
+
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
+        events.expectLogin().user(userId).detail(Details.USERNAME, username).assertEvent();
+
         oauth.openLogout();
 
+        events.expectLogout().user(userId).assertEvent();
+
         loginPage.open();
 
-        loginPage.login("login@test.com", "resetPassword");
+        loginPage.login("login-test", "resetPassword");
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
     }
@@ -182,6 +175,8 @@ public class ResetPasswordTest {
         Thread.sleep(1000);
 
         Assert.assertEquals(0, greenMail.getReceivedMessages().length);
+
+        events.expectRequiredAction("send_reset_password").user((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
     }
 
     @Test
@@ -211,6 +206,8 @@ public class ResetPasswordTest {
         String body = (String) message.getContent();
         String changePasswordUrl = body.split("\n")[3];
 
+        events.expectRequiredAction("send_reset_password").user(userId).detail(Details.USERNAME, "login-test").detail(Details.EMAIL, "login@test.com").assertEvent();
+
         driver.navigate().to(changePasswordUrl.trim());
 
         updatePasswordPage.assertCurrent();
@@ -221,14 +218,23 @@ public class ResetPasswordTest {
 
         updatePasswordPage.changePassword("resetPasswordWithPasswordPolicy", "resetPasswordWithPasswordPolicy");
 
+        events.expectRequiredAction("update_password").user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
+
         oauth.openLogout();
 
+        events.expectLogout().user(userId).assertEvent();
+
         loginPage.open();
 
         loginPage.login("login-test", "resetPasswordWithPasswordPolicy");
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent();
     }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
index a0adaf2..c02794e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SSOTest.java
@@ -26,6 +26,8 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.audit.Details;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
 import org.keycloak.testsuite.pages.AppPage;
@@ -63,6 +65,9 @@ public class SSOTest {
     @WebResource
     protected AccountUpdateProfilePage profilePage;
 
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
     @Test
     public void loginSuccess() {
         loginPage.open();
@@ -71,6 +76,8 @@ public class SSOTest {
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
         Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
 
+        events.expectLogin().assertEvent();
+
         appPage.open();
 
         oauth.openLoginForm();
@@ -80,6 +87,9 @@ public class SSOTest {
         profilePage.open();
 
         Assert.assertTrue(profilePage.isCurrent());
+
+        events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("test-app").assertEvent();
+        events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("account").detail(Details.REDIRECT_URI, "http://localhost:8081/auth/rest/realms/test/account/login-redirect").assertEvent();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index fa96732..4829497 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -26,7 +26,10 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.audit.Details;
+import org.keycloak.audit.Event;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -59,10 +62,15 @@ public class AccessTokenTest {
     @WebResource
     protected LoginPage loginPage;
 
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
     @Test
     public void accessTokenRequest() throws Exception {
         oauth.doLogin("test-user@localhost", "password");
 
+        String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
+
         String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
         AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
 
@@ -82,6 +90,28 @@ public class AccessTokenTest {
 
         Assert.assertEquals(1, token.getResourceAccess(oauth.getClientId()).getRoles().size());
         Assert.assertTrue(token.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
+
+        Event event = events.expectCodeToToken(codeId).assertEvent();
+        Assert.assertEquals(token.getId(), event.getDetails().get(Details.TOKEN_ID));
+        Assert.assertEquals(oauth.verifyRefreshToken(response.getRefreshToken()).getId(), event.getDetails().get(Details.REFRESH_TOKEN_ID));
+
+        response = oauth.doAccessTokenRequest(code, "password");
+        Assert.assertEquals(400, response.getStatusCode());
+
+        events.expectCodeToToken(codeId).error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null).assertEvent();
+    }
+
+    @Test
+    public void accessTokenInvalidClientCredentials() throws Exception {
+        oauth.doLogin("test-user@localhost", "password");
+
+        String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
+
+        String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+        AccessTokenResponse response = oauth.doAccessTokenRequest(code, "invalid");
+        Assert.assertEquals(400, response.getStatusCode());
+
+        events.expectCodeToToken(codeId).error("invalid_client_credentials").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).assertEvent();
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index d76d189..7bb9c68 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -26,13 +26,14 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
-import org.keycloak.models.ApplicationModel;
+import org.keycloak.audit.Details;
+import org.keycloak.jose.jws.JWSInput;
 import org.keycloak.models.Constants;
 import org.keycloak.models.RealmModel;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AuthorizationCodeResponse;
-import org.keycloak.testsuite.pages.ErrorPage;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.WebResource;
@@ -62,8 +63,8 @@ public class AuthorizationCodeTest {
     @WebResource
     protected LoginPage loginPage;
 
-    @WebResource
-    protected ErrorPage errorPage;
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
 
     @Test
     public void authorizationRequest() throws IOException {
@@ -77,6 +78,9 @@ public class AuthorizationCodeTest {
         Assert.assertNull(response.getError());
 
         oauth.verifyCode(response.getCode());
+
+        String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
+        Assert.assertEquals(codeId, new JWSInput(response.getCode()).readContentAsString());
     }
 
     @Test
@@ -90,6 +94,9 @@ public class AuthorizationCodeTest {
 
         String code = driver.findElement(By.id(OAuth2Constants.CODE)).getText();
         oauth.verifyCode(code);
+
+        String codeId = events.expectLogin().detail(Details.REDIRECT_URI, Constants.INSTALLED_APP_URN).assertEvent().getDetails().get(Details.CODE_ID);
+        Assert.assertEquals(codeId, new JWSInput(code).readContentAsString());
     }
 
     @Test
@@ -109,6 +116,9 @@ public class AuthorizationCodeTest {
         Assert.assertNotNull(response.getCode());
 
         oauth.verifyCode(response.getCode());
+
+        String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
+        Assert.assertEquals(codeId, new JWSInput(response.getCode()).readContentAsString());
     }
 
     @Test
@@ -121,6 +131,9 @@ public class AuthorizationCodeTest {
         Assert.assertNull(response.getError());
 
         oauth.verifyCode(response.getCode());
+
+        String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
+        Assert.assertEquals(codeId, new JWSInput(response.getCode()).readContentAsString());
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index cd43cb4..2072e6e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -21,15 +21,14 @@
  */
 package org.keycloak.testsuite.oauth;
 
-import java.io.IOException;
-import java.util.Map;
-
 import org.junit.Assert;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.audit.Details;
 import org.keycloak.representations.AccessToken;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.OAuthGrantPage;
@@ -38,6 +37,9 @@ import org.keycloak.testsuite.rule.WebResource;
 import org.keycloak.testsuite.rule.WebRule;
 import org.openqa.selenium.WebDriver;
 
+import java.io.IOException;
+import java.util.Map;
+
 /**
  * @author <a href="mailto:vrockai@redhat.com">Viliam Rockai</a>
  */
@@ -47,6 +49,9 @@ public class OAuthGrantTest {
     public static KeycloakRule keycloakRule = new KeycloakRule();
 
     @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
+    @Rule
     public WebRule webRule = new WebRule(this);
 
     @WebResource
@@ -76,6 +81,9 @@ public class OAuthGrantTest {
         grantPage.accept();
 
         Assert.assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.CODE));
+
+        String codeId = events.expectLogin().client("third-party").assertEvent().getDetails().get(Details.CODE_ID);
+
         OAuthClient.AccessTokenResponse accessToken = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
 
         AccessToken token = oauth.verifyToken(accessToken.getAccessToken());
@@ -88,6 +96,8 @@ public class OAuthGrantTest {
         Assert.assertEquals(1, resourceAccess.size());
         Assert.assertEquals(1, resourceAccess.get("test-app").getRoles().size());
         Assert.assertTrue(resourceAccess.get("test-app").isUserInRole("customer-user"));
+
+        events.expectCodeToToken(codeId).client("third-party").assertEvent();
     }
 
     @Test
@@ -103,5 +113,8 @@ public class OAuthGrantTest {
 
         Assert.assertTrue(oauth.getCurrentQuery().containsKey(OAuth2Constants.ERROR));
         Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR));
+
+        events.expectLogin().client("third-party").error("rejected_by_user").assertEvent();
     }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
index 5780874..beb8e77 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java
@@ -26,8 +26,11 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
+import org.keycloak.audit.Details;
+import org.keycloak.audit.Event;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.RefreshToken;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
 import org.keycloak.testsuite.pages.LoginPage;
@@ -61,10 +64,15 @@ public class RefreshTokenTest {
     @WebResource
     protected LoginPage loginPage;
 
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
     @Test
     public void refreshTokenRequest() throws Exception {
         oauth.doLogin("test-user@localhost", "password");
 
+        String codeId = events.expectLogin().assertEvent().getDetails().get(Details.CODE_ID);
+
         String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
 
         AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
@@ -72,6 +80,8 @@ public class RefreshTokenTest {
         String refreshTokenString = tokenResponse.getRefreshToken();
         RefreshToken refreshToken = oauth.verifyRefreshToken(refreshTokenString);
 
+        Event tokenEvent = events.expectCodeToToken(codeId).assertEvent();
+
         Assert.assertNotNull(refreshTokenString);
 
         Assert.assertEquals("bearer", tokenResponse.getTokenType());
@@ -106,6 +116,10 @@ public class RefreshTokenTest {
 
         Assert.assertEquals(1, refreshedToken.getResourceAccess(oauth.getClientId()).getRoles().size());
         Assert.assertTrue(refreshedToken.getResourceAccess(oauth.getClientId()).isUserInRole("customer-user"));
+
+        Event refreshEvent = events.expectRefresh(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID)).assertEvent();
+        Assert.assertNotEquals(tokenEvent.getDetails().get(Details.TOKEN_ID), refreshEvent.getDetails().get(Details.TOKEN_ID));
+        Assert.assertNotEquals(tokenEvent.getDetails().get(Details.REFRESH_TOKEN_ID), refreshEvent.getDetails().get(Details.UPDATED_REFRESH_TOKEN_ID));
     }
 
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
index 980048a..0855be7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java
@@ -7,9 +7,9 @@ import io.undertow.servlet.api.ServletInfo;
 import io.undertow.servlet.api.WebResourceCollection;
 import org.junit.rules.ExternalResource;
 import org.keycloak.models.Config;
-import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.services.managers.ModelToRepresentation;
@@ -40,7 +40,8 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
     public UserRepresentation getUser(String realm, String name) {
         KeycloakSession session = server.getKeycloakSessionFactory().createSession();
         try {
-            return ModelToRepresentation.toRepresentation(session.getRealmByName(realm).getUser(name));
+            UserModel user = session.getRealmByName(realm).getUser(name);
+            return user != null ? ModelToRepresentation.toRepresentation(user) : null;
         } finally {
             session.close();
         }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
index 3e6e7af..e547e13 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java
@@ -22,12 +22,8 @@
 package org.keycloak.testsuite.rule;
 
 import org.keycloak.models.Config;
-import org.keycloak.models.Constants;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.representations.idm.UserRepresentation;
-import org.keycloak.services.managers.ModelToRepresentation;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.ApplicationServlet;
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
index 25e1cd5..8424550 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/social/SocialLoginTest.java
@@ -27,13 +27,12 @@ import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.keycloak.OAuth2Constants;
-import org.keycloak.models.AccountRoles;
-import org.keycloak.models.ApplicationModel;
-import org.keycloak.models.Constants;
+import org.keycloak.audit.Details;
 import org.keycloak.models.RealmModel;
 import org.keycloak.representations.AccessToken;
 import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.AssertEvents;
 import org.keycloak.testsuite.DummySocialServlet;
 import org.keycloak.testsuite.OAuthClient;
 import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
@@ -87,6 +86,9 @@ public class SocialLoginTest {
     @WebResource
     protected OAuthClient oauth;
 
+    @Rule
+    public AssertEvents events = new AssertEvents(keycloakRule);
+
     @BeforeClass
     public static void before() {
         keycloakRule.deployServlet("dummy-social", "/dummy-social", DummySocialServlet.class);
@@ -107,8 +109,21 @@ public class SocialLoginTest {
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
+        String userId = events.expect("register")
+                .user(AssertEvents.isUUID())
+                .detail(Details.EMAIL, "bob@builder.com")
+                .detail(Details.RESPONSE_TYPE, "code")
+                .detail(Details.REGISTER_METHOD, "social")
+                .detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
+                .detail(Details.USERNAME, "1@dummy")
+                .assertEvent().getUserId();
+
+        String codeId = events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social").assertEvent().getDetails().get(Details.CODE_ID);
+
         AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
 
+        events.expectCodeToToken(codeId).user(userId).assertEvent();
+
         AccessToken token = oauth.verifyToken(response.getAccessToken());
         Assert.assertEquals(36, token.getSubject().length());
 
@@ -118,8 +133,21 @@ public class SocialLoginTest {
         Assert.assertEquals("Bob", profile.getFirstName());
         Assert.assertEquals("Builder", profile.getLastName());
         Assert.assertEquals("bob@builder.com", profile.getEmail());
-    }
 
+        oauth.openLogout();
+
+        events.expectLogout().user(userId).assertEvent();
+
+        loginPage.open();
+
+        loginPage.clickSocial("dummy");
+
+        driver.findElement(By.id("id")).sendKeys("1");
+        driver.findElement(By.id("username")).sendKeys("dummy-user1");
+        driver.findElement(By.id("login")).click();
+
+        events.expectLogin().user(userId).detail(Details.USERNAME, "1@dummy").detail(Details.AUTH_METHOD, "social").assertEvent();
+    }
 
     @Test
     public void loginCancelled() throws Exception {
@@ -132,9 +160,13 @@ public class SocialLoginTest {
         Assert.assertTrue(loginPage.isCurrent());
         Assert.assertEquals("Access denied", loginPage.getWarning());
 
+        events.expectLogin().error("rejected_by_user").user((String) null).detail(Details.AUTH_METHOD, "social").removeDetail(Details.USERNAME).removeDetail(Details.CODE_ID).assertEvent();
+
         loginPage.login("test-user@localhost", "password");
 
         Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+        events.expectLogin().assertEvent();
     }
 
     @Test
@@ -164,13 +196,29 @@ public class SocialLoginTest {
             Assert.assertEquals("Builder", profilePage.getLastName());
             Assert.assertEquals("bob@builder.com", profilePage.getEmail());
 
+            String userId = events.expect("register")
+                    .user(AssertEvents.isUUID())
+                    .detail(Details.EMAIL, "bob@builder.com")
+                    .detail(Details.RESPONSE_TYPE, "code")
+                    .detail(Details.REGISTER_METHOD, "social")
+                    .detail(Details.REDIRECT_URI, AssertEvents.DEFAULT_REDIRECT_URI)
+                    .detail(Details.USERNAME, "2@dummy")
+                    .assertEvent().getUserId();
+
             profilePage.update("Dummy", "User", "dummy-user-reg@dummy-social");
 
+            events.expectRequiredAction("update_profile").user(userId).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").assertEvent();
+            events.expectRequiredAction("update_email").user(userId).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").detail(Details.PREVIOUS_EMAIL, "bob@builder.com").detail(Details.UPDATED_EMAIL, "dummy-user-reg@dummy-social").assertEvent();
+
             Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
 
+            String codeId = events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "social").detail(Details.USERNAME, "2@dummy").assertEvent().getDetails().get(Details.CODE_ID);
+
             AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get(OAuth2Constants.CODE), "password");
             AccessToken token = oauth.verifyToken(response.getAccessToken());
 
+            events.expectCodeToToken(codeId).user(userId).assertEvent();
+
             UserRepresentation profile = keycloakRule.getUserById("test", token.getSubject());
 
             Assert.assertEquals("Dummy", profile.getFirstName());
diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory
new file mode 100644
index 0000000..7389c6f
--- /dev/null
+++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory
@@ -0,0 +1 @@
+org.keycloak.testsuite.AssertEvents
\ No newline at end of file
diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml
index da21ebc..8480c59 100755
--- a/testsuite/performance/pom.xml
+++ b/testsuite/performance/pom.xml
@@ -78,8 +78,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>1.6</source>
-                    <target>1.6</target>
+                    <source>${maven.compiler.source}</source>
+                    <target>${maven.compiler.target}</target>
                 </configuration>
             </plugin>
             <plugin>
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 6d9ccb0..b5bc8e6 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
-<project>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 	<parent>
 		<artifactId>keycloak-parent</artifactId>
 		<groupId>org.keycloak</groupId>