keycloak-aplcache
Changes
admin-ui/pom.xml 7(+4 -3)
admin-ui-styles/pom.xml 7(+4 -3)
audit/api/pom.xml 39(+39 -0)
audit/jboss-logging/pom.xml 39(+39 -0)
audit/jboss-logging/src/main/java/org/keycloak/audit/log/JBossLoggingAuditListenerFactory.java 34(+34 -0)
audit/jboss-logging/src/main/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory 1(+1 -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)
core-jaxrs/pom.xml 7(+4 -3)
distribution/appliance-dist/pom.xml 3(+2 -1)
distribution/as7-adapter-zip/pom.xml 3(+2 -1)
distribution/pom.xml 1(+0 -1)
distribution/src-dist/pom.xml 3(+2 -1)
distribution/war-dist/pom.xml 3(+2 -1)
distribution/war-zip/pom.xml 3(+2 -1)
examples/demo-template/pom.xml 1(+0 -1)
examples/js-console/pom.xml 4(+2 -2)
examples/pom.xml 1(+0 -1)
forms/account-api/pom.xml 7(+4 -3)
forms/account-freemarker/pom.xml 7(+4 -3)
forms/common-freemarker/pom.xml 7(+4 -3)
forms/common-themes/pom.xml 7(+4 -3)
forms/login-api/pom.xml 7(+4 -3)
forms/login-freemarker/pom.xml 7(+4 -3)
forms/pom.xml 3(+2 -1)
integration/adapter-core/pom.xml 7(+4 -3)
integration/as7-eap6/adapter/pom.xml 7(+4 -3)
integration/as7-eap-subsystem/pom.xml 10(+3 -7)
integration/installed/pom.xml 7(+4 -3)
integration/js/pom.xml 7(+4 -3)
integration/pom.xml 1(+0 -1)
integration/undertow/pom.xml 7(+4 -3)
integration/wildfly-adapter/pom.xml 7(+4 -3)
integration/wildfly-subsystem/pom.xml 10(+3 -7)
model/api/pom.xml 7(+4 -3)
model/jpa/pom.xml 7(+4 -3)
model/mongo/pom.xml 4(+2 -2)
model/picketlink/pom.xml 7(+4 -3)
model/pom.xml 3(+1 -2)
pom.xml 14(+10 -4)
server/pom.xml 25(+22 -3)
services/pom.xml 13(+10 -3)
social/core/pom.xml 3(+2 -1)
social/google/pom.xml 3(+2 -1)
social/pom.xml 3(+2 -1)
social/twitter/pom.xml 3(+2 -1)
spi/authentication-model/pom.xml 4(+2 -2)
spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/AbstractModelAuthenticationProvider.java 30(+15 -15)
spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ExternalModelAuthenticationProvider.java 9(+1 -8)
spi/authentication-model/src/main/java/org/keycloak/spi/authentication/model/ModelAuthenticationProvider.java 8(+1 -7)
spi/authentication-picketlink/src/main/java/org/keycloak/spi/authentication/picketlink/PicketlinkAuthenticationProvider.java 200(+99 -101)
spi/authentication-spi/pom.xml 4(+2 -2)
spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProvider.java 13(+12 -1)
spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthenticationProviderManager.java 143(+98 -45)
spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderConstants.java 3(+1 -2)
spi/authentication-spi/src/main/java/org/keycloak/spi/authentication/AuthProviderStatus.java 2(+1 -1)
testsuite/integration/pom.xml 17(+14 -3)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java 70(+57 -13)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java 16(+16 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionResetPasswordTest.java 12(+12 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java 39(+39 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java 44(+31 -13)
testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java 3(+3 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/forms/AuthProvidersIntegrationTest.java 20(+10 -10)
testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java 21(+17 -4)
testsuite/integration/src/test/resources/META-INF/services/org.keycloak.audit.AuditListenerFactory 1(+1 -0)
testsuite/performance/pom.xml 4(+2 -2)
testsuite/pom.xml 3(+2 -1)
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
admin-ui-styles/pom.xml 7(+4 -3)
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";
+
+}
audit/jboss-logging/pom.xml 39(+39 -0)
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);
}
core-jaxrs/pom.xml 7(+4 -3)
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>
distribution/appliance-dist/pom.xml 3(+2 -1)
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>
distribution/as7-adapter-zip/pom.xml 3(+2 -1)
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>
distribution/pom.xml 1(+0 -1)
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>
distribution/src-dist/pom.xml 3(+2 -1)
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>
distribution/war-dist/pom.xml 3(+2 -1)
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>
distribution/war-zip/pom.xml 3(+2 -1)
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>
examples/demo-template/pom.xml 1(+0 -1)
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>
examples/js-console/pom.xml 4(+2 -2)
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>
forms/account-api/pom.xml 7(+4 -3)
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>
forms/account-freemarker/pom.xml 7(+4 -3)
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>
forms/common-freemarker/pom.xml 7(+4 -3)
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>
forms/common-themes/pom.xml 7(+4 -3)
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>
forms/login-api/pom.xml 7(+4 -3)
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>
forms/login-freemarker/pom.xml 7(+4 -3)
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>
integration/adapter-core/pom.xml 7(+4 -3)
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>
integration/as7-eap6/adapter/pom.xml 7(+4 -3)
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>
integration/as7-eap-subsystem/pom.xml 10(+3 -7)
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>
integration/installed/pom.xml 7(+4 -3)
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>
integration/js/pom.xml 7(+4 -3)
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>
integration/pom.xml 1(+0 -1)
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>
integration/undertow/pom.xml 7(+4 -3)
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>
integration/wildfly-adapter/pom.xml 7(+4 -3)
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>
integration/wildfly-subsystem/pom.xml 10(+3 -7)
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>
model/api/pom.xml 7(+4 -3)
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);
}
model/jpa/pom.xml 7(+4 -3)
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();
+ }
}
model/mongo/pom.xml 4(+2 -2)
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
model/picketlink/pom.xml 7(+4 -3)
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);
}
social/core/pom.xml 3(+2 -1)
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>
social/google/pom.xml 3(+2 -1)
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>
social/twitter/pom.xml 3(+2 -1)
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>
spi/authentication-model/pom.xml 4(+2 -2)
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;
+ }
+}
spi/authentication-spi/pom.xml 4(+2 -2)
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
}
testsuite/integration/pom.xml 17(+14 -3)
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
testsuite/performance/pom.xml 4(+2 -2)
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>
testsuite/pom.xml 3(+2 -1)
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>