diff --git a/.gitignore b/.gitignore
index b139121..764fa81 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
# Just so that we don't store the docker image build output into the repository.
-mvn-output/
\ No newline at end of file
+mvn-output/
+target/classes/**/*.class
diff --git a/README.md b/README.md
index e370680..b2c9d02 100755
--- a/README.md
+++ b/README.md
@@ -33,6 +33,18 @@ sudo chown $USER:$USER mvn-output
# Deploy
* Copy target/event-listener-http-jar-with-dependencies.jar to {KEYCLOAK_HOME}/standalone/deployments
+
+# Configuration
+
+## Option 1
+* Configure the following env variables :
+
+ - HTTP_EVENT_SERVERURI - default: http://127.0.0.1:8080/webhook
+ - HTTP_EVENT_USERNAME - default: keycloak
+ - HTTP_EVENT_PASSWORD - default: keycloak
+
+* Restart the keycloak server.
+## Option 2
* Edit standalone.xml to configure the Webhook settings. Find the following
section in the configuration:
@@ -50,17 +62,16 @@ And add below:
-
```
+
Leave username and password out if the service allows anonymous access.
-If unset, the default message topic is "keycloak/events".
* Restart the keycloak server.
-# Use
+# Usage
Add/Update a user, your webhook should be called, looks at the keycloak syslog for debug
Request example
diff --git a/pom.xml b/pom.xml
index 8d01d3e..294f75f 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1,24 +1,7 @@
-
-
org.softwarefactory.keycloak.providers.events.http
- 5.0.0
+ 13.0.0
Keycloak: Event Publisher to HTTP
@@ -32,35 +15,16 @@
${project.version}
1.2.2.Final
- 1.0.2.Final
- 1.0.1.Final
7.4.Final
2.6
1.4.1
- 2.19.1
- 1.6.0
- 1.8
- 1.4
3.0.2
3.1
- 2.3.2
- 1.2.1.Final
- 2.0.2.Final
- 8.2.1.Final
- 4.12
- 1.3
- 1.6.1
2.9.5
- true
./jboss-cli.sh
10090
- 3.11.0
- 1.4.0.Final
- 2.5.1
- 1.1.2
- 1.0.1
@@ -82,40 +46,10 @@
provided
- org.jboss.arquillian.protocol
- arquillian-protocol-servlet
- 1.4.1.Final
- test
-
-
- org.jboss.arquillian.graphene
- graphene-webdriver
- ${arquillian-graphene.version}
- pom
- test
-
-
- org.jboss.arquillian.extension
- arquillian-phantom-driver
- ${arquillian-phantom.version}
- test
-
-
- org.wildfly.extras.creaper
- creaper-core
- ${version.creaper}
- test
-
-
- com.google.guava
- guava
-
-
-
-
- org.hamcrest
- hamcrest-all
- 1.3
+ org.keycloak
+ keycloak-services
+ provided
+ ${version.keycloak}
com.squareup.okhttp3
@@ -137,17 +71,6 @@
1.8
-
- org.apache.maven.plugins
- maven-surefire-plugin
- ${version.surefire.plugin}
-
-
- ${keycloak.management.port}
- ${project.build.directory}
-
-
-
org.apache.maven.plugins
diff --git a/src/main/java/org/softwarefactory/keycloak/providers/events/http/AdminEventNotification.java b/src/main/java/org/softwarefactory/keycloak/providers/events/http/AdminEventNotification.java
new file mode 100644
index 0000000..e16d766
--- /dev/null
+++ b/src/main/java/org/softwarefactory/keycloak/providers/events/http/AdminEventNotification.java
@@ -0,0 +1,31 @@
+package org.softwarefactory.keycloak.providers.events.http;
+
+import java.io.Serializable;
+
+import org.keycloak.events.admin.AdminEvent;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = Id.CLASS)
+public class AdminEventNotification extends AdminEvent implements Serializable {
+
+ private static final long serialVersionUID = -7367949289101799624L;
+
+ public static AdminEventNotification create(AdminEvent adminEvent) {
+ AdminEventNotification msg = new AdminEventNotification();
+ msg.setAuthDetails(adminEvent.getAuthDetails());
+ msg.setError(adminEvent.getError());
+ msg.setOperationType(adminEvent.getOperationType());
+ msg.setRealmId(adminEvent.getRealmId());
+ msg.setRepresentation(adminEvent.getRepresentation());
+ msg.setResourcePath(adminEvent.getResourcePath());
+ msg.setResourceType(adminEvent.getResourceType());
+ msg.setTime(adminEvent.getTime());
+ return msg;
+ }
+
+
+}
diff --git a/src/main/java/org/softwarefactory/keycloak/providers/events/http/ClientEventNotification.java b/src/main/java/org/softwarefactory/keycloak/providers/events/http/ClientEventNotification.java
new file mode 100644
index 0000000..b236cb7
--- /dev/null
+++ b/src/main/java/org/softwarefactory/keycloak/providers/events/http/ClientEventNotification.java
@@ -0,0 +1,33 @@
+package org.softwarefactory.keycloak.providers.events.http;
+
+import java.io.Serializable;
+
+import org.keycloak.events.Event;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = Id.CLASS)
+public class ClientEventNotification extends Event implements Serializable {
+
+ private static final long serialVersionUID = -2192461924304841222L;
+
+ public static ClientEventNotification create(Event event) {
+ ClientEventNotification msg = new ClientEventNotification();
+ msg.setClientId(event.getClientId());
+ msg.setDetails(event.getDetails());
+ msg.setError(event.getError());
+ msg.setIpAddress(event.getIpAddress());
+ msg.setRealmId(event.getRealmId());
+ msg.setSessionId(event.getSessionId());
+ msg.setTime(event.getTime());
+ msg.setType(event.getType());
+ msg.setUserId(event.getUserId());
+
+ return msg;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventConfiguration.java b/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventConfiguration.java
new file mode 100644
index 0000000..27ba024
--- /dev/null
+++ b/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventConfiguration.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.softwarefactory.keycloak.providers.events.http;
+
+
+import org.keycloak.Config.Scope;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @author Abdoulaye Traore
+ */
+public class HTTPEventConfiguration {
+
+ private String serverUri;
+ private String username;
+ private String password;
+
+ public static final ObjectMapper httpEventConfigurationObjectMapper = new ObjectMapper();
+
+ public static HTTPEventConfiguration createFromScope(Scope config) {
+ HTTPEventConfiguration configuration = new HTTPEventConfiguration();
+
+ configuration.serverUri = resolveConfigVar(config, "serverUri", "http://127.0.0.1:8080/webhook");
+ configuration.username = resolveConfigVar(config, "username", "keycloak");
+ configuration.password = resolveConfigVar(config, "password", "keycloak");
+
+ return configuration;
+
+ }
+
+ private static String resolveConfigVar(Scope config, String variableName, String defaultValue) {
+
+ String value = defaultValue;
+ if(config != null && config.get(variableName) != null) {
+ value = config.get(variableName);
+ } else {
+ //try from env variables eg: HTTP_EVENT_:
+ String envVariableName = "HTTP_EVENT_" + variableName.toUpperCase();
+ if(System.getenv(envVariableName) != null) {
+ value = System.getenv(envVariableName);
+ }
+ }
+ System.out.println("HTTPEventListener configuration: " + variableName + "=" + value);
+ return value;
+
+ }
+
+ public static String writeAsJson(Object object, boolean isPretty) {
+ String messageAsJson = "unparsable";
+ try {
+ if(isPretty) {
+ messageAsJson = HTTPEventConfiguration.httpEventConfigurationObjectMapper
+ .writerWithDefaultPrettyPrinter().writeValueAsString(object);
+ } else {
+ messageAsJson = HTTPEventConfiguration.httpEventConfigurationObjectMapper
+ .writeValueAsString(object);
+ }
+
+ } catch (JsonProcessingException e) {
+ e.printStackTrace();
+ }
+ return messageAsJson;
+ }
+
+
+
+ public String getServerUri() {
+ return serverUri;
+ }
+ public void setServerUri(String serverUri) {
+ this.serverUri = serverUri;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+ public void setUsername(String username) {
+ this.username = username;
+ }
+ public String getPassword() {
+ return password;
+ }
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+
+}
diff --git a/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.java b/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.java
index 9b45e36..cfebc62 100755
--- a/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.java
+++ b/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.java
@@ -17,41 +17,48 @@
package org.softwarefactory.keycloak.providers.events.http;
+import java.io.IOException;
+import java.util.Set;
+
import org.keycloak.events.Event;
import org.keycloak.events.EventListenerProvider;
+import org.keycloak.events.EventListenerTransaction;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.OperationType;
+import org.keycloak.models.KeycloakSession;
-import java.util.Map;
-import java.util.Set;
-import java.lang.Exception;
-
-import okhttp3.*;
-import okhttp3.OkHttpClient.Builder;
-
-import java.io.IOException;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
/**
- * @author Jessy Lenne
+ * @author Abdoulaye Traore
*/
public class HTTPEventListenerProvider implements EventListenerProvider {
+
private final OkHttpClient httpClient = new OkHttpClient();
private Set excludedEvents;
private Set excludedAdminOperations;
private String serverUri;
private String username;
private String password;
- public static final String publisherId = "keycloak";
- public String TOPIC;
- public HTTPEventListenerProvider(Set excludedEvents, Set excludedAdminOperations, String serverUri, String username, String password, String topic) {
+ private KeycloakSession session;
+
+ private EventListenerTransaction tx = new EventListenerTransaction(this::publishAdminEvent, this::publishEvent);
+
+ public HTTPEventListenerProvider(Set excludedEvents, Set excludedAdminOperations, String serverUri, String username, String password, KeycloakSession session) {
this.excludedEvents = excludedEvents;
this.excludedAdminOperations = excludedAdminOperations;
this.serverUri = serverUri;
this.username = username;
this.password = password;
- this.TOPIC = topic;
+
+ this.session = session;
+ this.session.getTransactionManager().enlistAfterCompletion(tx);
}
@Override
@@ -60,145 +67,66 @@ public void onEvent(Event event) {
if (excludedEvents != null && excludedEvents.contains(event.getType())) {
return;
} else {
- String stringEvent = toString(event);
- try {
- RequestBody formBody = new FormBody.Builder()
- .add("json", stringEvent)
- .build();
-
- okhttp3.Request.Builder builder = new Request.Builder()
- .url(this.serverUri)
- .addHeader("User-Agent", "KeycloakHttp Bot");
-
-
- if (this.username != null && this.password != null) {
- builder.addHeader("Authorization", "Basic " + this.username + ":" + this.password.toCharArray());
- }
-
- Request request = builder.post(formBody)
- .build();
-
- Response response = httpClient.newCall(request).execute();
-
- if (!response.isSuccessful()) {
- throw new IOException("Unexpected code " + response);
- }
-
- // Get response body
- System.out.println(response.body().string());
- } catch(Exception e) {
- // ?
- System.out.println("UH OH!! " + e.toString());
- e.printStackTrace();
- return;
- }
+ tx.addEvent(event);
}
}
@Override
- public void onEvent(AdminEvent event, boolean includeRepresentation) {
+ public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
// Ignore excluded operations
- if (excludedAdminOperations != null && excludedAdminOperations.contains(event.getOperationType())) {
+ if (excludedAdminOperations != null && excludedAdminOperations.contains(adminEvent.getOperationType())) {
return;
} else {
- String stringEvent = toString(event);
- try {
- RequestBody formBody = new FormBody.Builder()
- .add("json", stringEvent)
- .build();
-
- okhttp3.Request.Builder builder = new Request.Builder()
- .url(this.serverUri)
- .addHeader("User-Agent", "KeycloakHttp Bot");
-
-
- if (this.username != null && this.password != null) {
- builder.addHeader("Authorization", "Basic " + this.username + ":" + this.password.toCharArray());
- }
-
- Request request = builder.post(formBody)
- .build();
-
- Response response = httpClient.newCall(request).execute();
-
- if (!response.isSuccessful()) {
- throw new IOException("Unexpected code " + response);
- }
-
- // Get response body
- System.out.println(response.body().string());
- } catch(Exception e) {
- // ?
- System.out.println("UH OH!! " + e.toString());
- e.printStackTrace();
- return;
- }
+ tx.addAdminEvent(adminEvent, includeRepresentation);
}
}
+ public void publishEvent(Event event) {
+ ClientEventNotification notification = ClientEventNotification.create(event);
+ String notificationAsString = HTTPEventConfiguration.writeAsJson(notification, false);
+ this.sendEvent(notificationAsString);
+ }
- private String toString(Event event) {
- StringBuilder sb = new StringBuilder();
-
- sb.append("{'type': '");
- sb.append(event.getType());
- 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());
- sb.append("'");
-
- if (event.getError() != null) {
- sb.append(", 'error': '");
- sb.append(event.getError());
- sb.append("'");
- }
- sb.append(", 'details': {");
- if (event.getDetails() != null) {
- for (Map.Entry e : event.getDetails().entrySet()) {
- sb.append("'");
- sb.append(e.getKey());
- sb.append("': '");
- sb.append(e.getValue());
- sb.append("', ");
+ public void publishAdminEvent(AdminEvent event, boolean includeRepresentation) {
+ AdminEventNotification notification = AdminEventNotification.create(event);
+ String notificationAsString = HTTPEventConfiguration.writeAsJson(notification, false);
+ this.sendEvent(notificationAsString);
+ }
+
+ private void sendEvent(String event) {
+ try {
+ MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+
+ RequestBody formBody = RequestBody.create(event, JSON);
+
+ okhttp3.Request.Builder builder = new Request.Builder()
+ .url(this.serverUri)
+ .addHeader("User-Agent", "KeycloakHttp Bot");
+
+
+ if (this.username != null && this.password != null) {
+ builder.addHeader("Authorization", "Basic " + this.username + ":" + this.password.toCharArray());
+ }
+
+ Request request = builder.post(formBody)
+ .build();
+
+ Response response = httpClient.newCall(request).execute();
+
+ if (!response.isSuccessful()) {
+ throw new IOException("Unexpected code " + response);
}
- sb.append("}}");
- }
- return sb.toString();
- }
-
-
- private String toString(AdminEvent adminEvent) {
- StringBuilder sb = new StringBuilder();
-
- sb.append("{'type': '");
- sb.append(adminEvent.getOperationType());
- sb.append("', 'realmId': '");
- sb.append(adminEvent.getAuthDetails().getRealmId());
- sb.append("', 'clientId': '");
- sb.append(adminEvent.getAuthDetails().getClientId());
- sb.append("', 'userId': '");
- sb.append(adminEvent.getAuthDetails().getUserId());
- sb.append("', 'ipAddress': '");
- sb.append(adminEvent.getAuthDetails().getIpAddress());
- sb.append("', 'resourcePath': '");
- sb.append(adminEvent.getResourcePath());
- sb.append("'");
-
- if (adminEvent.getError() != null) {
- sb.append(", 'error': '");
- sb.append(adminEvent.getError());
- sb.append("'");
+ // Get response body
+ System.out.println(response.body().string());
+ } catch(Exception e) {
+ System.out.println("An error occured while sending event : " + e.toString());
+ e.printStackTrace();
+ return;
}
- sb.append("}");
- return sb.toString();
}
+
@Override
public void close() {
}
diff --git a/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.java b/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.java
index 101a22d..8065f1a 100755
--- a/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.java
+++ b/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.java
@@ -27,10 +27,9 @@
import java.util.HashSet;
import java.util.Set;
-import java.lang.Exception;
/**
- * @author Jessy Lennee
+ * @author Abdoulaye Traore
*/
public class HTTPEventListenerProviderFactory implements EventListenerProviderFactory {
@@ -39,11 +38,10 @@ public class HTTPEventListenerProviderFactory implements EventListenerProviderFa
private String serverUri;
private String username;
private String password;
- private String topic;
@Override
public EventListenerProvider create(KeycloakSession session) {
- return new HTTPEventListenerProvider(excludedEvents, excludedAdminOperations, serverUri, username, password, topic);
+ return new HTTPEventListenerProvider(excludedEvents, excludedAdminOperations, serverUri, username, password, session);
}
@Override
@@ -64,16 +62,19 @@ public void init(Config.Scope config) {
}
}
- serverUri = config.get("serverUri", "http://nginx/frontend_dev.php/webhook/keycloak");
- username = config.get("username", null);
- password = config.get("password", null);
- topic = config.get("topic", "keycloak/events");
+ HTTPEventConfiguration configuration = HTTPEventConfiguration.createFromScope(config);
+
+
+ serverUri = configuration.getServerUri();
+ username = configuration.getUsername();
+ password = configuration.getPassword();
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
+
@Override
public void close() {
}
diff --git a/target/classes/META-INF/services/org.keycloak.events.EventListenerProviderFactory b/target/classes/META-INF/services/org.keycloak.events.EventListenerProviderFactory
deleted file mode 100644
index cb8a9ef..0000000
--- a/target/classes/META-INF/services/org.keycloak.events.EventListenerProviderFactory
+++ /dev/null
@@ -1,19 +0,0 @@
-
-#
-# Copyright 2019 Red Hat, Inc. and/or its affiliates
-# and other contributors as indicated by the @author tags.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-org.softwarefactory.keycloak.providers.events.http.HTTPEventListenerProviderFactory
diff --git a/target/classes/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.class b/target/classes/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.class
deleted file mode 100644
index 5627ff0..0000000
Binary files a/target/classes/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.class and /dev/null differ
diff --git a/target/classes/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.class b/target/classes/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.class
deleted file mode 100644
index fd62952..0000000
Binary files a/target/classes/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.class and /dev/null differ
diff --git a/target/event-listener-http-jar-with-dependencies.jar b/target/event-listener-http-jar-with-dependencies.jar
deleted file mode 100644
index 67223ce..0000000
Binary files a/target/event-listener-http-jar-with-dependencies.jar and /dev/null differ
diff --git a/target/event-listener-http.jar b/target/event-listener-http.jar
deleted file mode 100644
index afc334f..0000000
Binary files a/target/event-listener-http.jar and /dev/null differ
diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties
deleted file mode 100644
index 33aa8ac..0000000
--- a/target/maven-archiver/pom.properties
+++ /dev/null
@@ -1,5 +0,0 @@
-#Generated by Maven
-#Tue Jan 28 10:33:34 CET 2020
-groupId=org.softwarefactory.keycloak.providers.events.http
-artifactId=event-listener-http
-version=5.0.0
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
deleted file mode 100644
index bf15e25..0000000
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
+++ /dev/null
@@ -1,2 +0,0 @@
-org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.class
-org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
deleted file mode 100644
index 8223f33..0000000
--- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
+++ /dev/null
@@ -1,2 +0,0 @@
-/Users/jessylenne/Documents/www/keycloak-event-listener-http/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProvider.java
-/Users/jessylenne/Documents/www/keycloak-event-listener-http/src/main/java/org/softwarefactory/keycloak/providers/events/http/HTTPEventListenerProviderFactory.java