mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-25 16:42:34 +00:00
Merge pull request #1805 from velias/KEYCLOAK-2033
KEYCLOAK-2033 - Allow custom SPI Providers to show additional info on
This commit is contained in:
@@ -1,18 +1,5 @@
|
||||
package org.keycloak.connections.jpa;
|
||||
|
||||
import org.hibernate.ejb.AvailableSettings;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.sql.DriverManager;
|
||||
@@ -22,10 +9,25 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.hibernate.ejb.AvailableSettings;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory {
|
||||
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory, ServerInfoAwareProviderFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.keycloak.connections.jpa;
|
||||
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface JpaConnectionProviderFactory extends ServerInfoAwareProviderFactory<JpaConnectionProvider> {
|
||||
public interface JpaConnectionProviderFactory extends ProviderFactory<JpaConnectionProvider> {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.keycloak.connections.mongo;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientOptions;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.ServerAddress;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
@@ -15,18 +16,19 @@ import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocati
|
||||
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.MongoClient;
|
||||
import com.mongodb.MongoClientOptions;
|
||||
import com.mongodb.MongoClientURI;
|
||||
import com.mongodb.MongoCredential;
|
||||
import com.mongodb.ServerAddress;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory {
|
||||
public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory, ServerInfoAwareProviderFactory {
|
||||
|
||||
// TODO Make it dynamic
|
||||
private String[] entities = new String[]{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package org.keycloak.connections.mongo;
|
||||
|
||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface MongoConnectionProviderFactory extends ServerInfoAwareProviderFactory<MongoConnectionProvider> {
|
||||
public interface MongoConnectionProviderFactory extends ProviderFactory<MongoConnectionProvider> {
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</para>
|
||||
<para>
|
||||
For example to implement the Event Listener SPI you start by implementing EventListenerProviderFactory:
|
||||
<programlisting><![CDATA[{
|
||||
<programlisting><![CDATA[
|
||||
package org.acme.provider;
|
||||
|
||||
import ...
|
||||
@@ -43,7 +43,7 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
|
||||
}
|
||||
|
||||
}
|
||||
}]]></programlisting>
|
||||
]]></programlisting>
|
||||
The example uses an imagined MaxList which has a maximum size and is concurrency safe. When the maximum size is reached
|
||||
and new entries are added the oldest entry is removed. Keycloak creates a single instance of
|
||||
EventListenerProviderFactory which makes it possible to store state for multiple requests. EventListenerProvider
|
||||
@@ -51,7 +51,7 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
|
||||
</para>
|
||||
<para>
|
||||
Next you would implement EventListenerProvider:
|
||||
<programlisting><![CDATA[{
|
||||
<programlisting><![CDATA[
|
||||
package org.acme.provider;
|
||||
|
||||
import ...
|
||||
@@ -75,13 +75,61 @@ public class MyEventListenerProvider implements EventListenerProvider {
|
||||
}
|
||||
|
||||
}
|
||||
}]]></programlisting>
|
||||
]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
The file <literal>META-INF/services/org.keycloak.events.EventListenerProviderFactory</literal> should
|
||||
contain the full name of your ProviderFactory implementation:
|
||||
<programlisting><![CDATA[org.acme.provider.MyEventListenerProviderFactory]]></programlisting>
|
||||
</para>
|
||||
|
||||
<section>
|
||||
<title>Show info from you SPI implementation in Keycloak admin console</title>
|
||||
<para>
|
||||
Sometimes it is useful to show additional info about your Provider to a Keycloak administrator.
|
||||
You can show provider build time informations (eg. version of custom provider currently installed),
|
||||
current configuration of the provider (eg. url of remote system your provider talks to) or some operational
|
||||
info (average time of response from remote system your provider talks to).
|
||||
Keycloak admin console provides Server Info page to show this kind of information.
|
||||
</para>
|
||||
<para>
|
||||
To show info from your provider it is enough to implement
|
||||
<literal>org.keycloak.provider.ServerInfoAwareProviderFactory</literal> interface in your ProviderFactory.
|
||||
Example implementation for MyEventListenerProviderFactory from previous example:
|
||||
<programlisting><![CDATA[
|
||||
package org.acme.provider;
|
||||
|
||||
import ...
|
||||
|
||||
public class MyEventListenerProviderFactory implements EventListenerProviderFactory, ServerInfoAwareProviderFactory {
|
||||
|
||||
private List<Event> events;
|
||||
private int max;
|
||||
|
||||
...
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
max = config.getInt("max");
|
||||
events = new MaxList(max);
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
@Override
|
||||
public Map<String, String> getOperationalInfo() {
|
||||
Map<String, String> ret = new LinkedHashMap<>();
|
||||
ret.put("version", "1.0");
|
||||
ret.put("listSizeMax", max + "");
|
||||
ret.put("listSizeCurrent", events.size() + "");
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
]]></programlisting>
|
||||
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -98,13 +146,13 @@ public class MyEventListenerProvider implements EventListenerProvider {
|
||||
To register a provider using Modules first create a module. To do this you can either use the jboss-cli
|
||||
script or manually create a folder inside KEYCLOAK_HOME/modules and add your jar and a <literal>module.xml</literal>.
|
||||
For example to add the event listener sysout example provider using the jboss-cli script execute:
|
||||
<programlisting><![CDATA[{
|
||||
<programlisting><![CDATA[
|
||||
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-model-api,org.keycloak.keycloak-events-api"
|
||||
}]]></programlisting>
|
||||
]]></programlisting>
|
||||
Or to manually create it start by creating the folder <literal>KEYCLOAK_HOME/modules/org/keycloak/examples/event-sysout/main</literal>.
|
||||
Then copy <literal>event-listener-sysout-example.jar</literal> to this folder and create <literal>module.xml</literal>
|
||||
with the following content:
|
||||
<programlisting><![CDATA[{
|
||||
<programlisting><![CDATA[
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.examples.event-sysout">
|
||||
<resources>
|
||||
@@ -116,7 +164,7 @@ public class MyEventListenerProvider implements EventListenerProvider {
|
||||
<module name="org.keycloak.keycloak-events-api"/>
|
||||
</dependencies>
|
||||
</module>
|
||||
}]]></programlisting>
|
||||
]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Once you've created the module you need to register this module with Keycloak. This is done by editing
|
||||
|
||||
@@ -3,15 +3,15 @@ package org.keycloak.provider;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Marker interface for ProviderFactory of Provider which wants to show some info on "Server Info" page in Admin console.
|
||||
* Marker interface for {@link ProviderFactory} of Provider which wants to show some info on "Server Info" page in Admin console.
|
||||
*
|
||||
* @author Vlastimil Elias (velias at redhat dot com)
|
||||
*/
|
||||
public interface ServerInfoAwareProviderFactory<T extends Provider> extends ProviderFactory<T> {
|
||||
public interface ServerInfoAwareProviderFactory {
|
||||
|
||||
/**
|
||||
* Get operational info about given provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is
|
||||
* shown on "Server Info" page.
|
||||
* Return actual info about the provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is
|
||||
* shown on "Server Info" page then.
|
||||
*
|
||||
* @return Map with keys describing value and relevant values itself
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
package org.keycloak.services.resources.admin.info;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.core.Context;
|
||||
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||
import org.keycloak.events.EventType;
|
||||
@@ -21,10 +33,6 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
||||
import org.keycloak.social.SocialIdentityProvider;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.core.Context;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@@ -73,7 +81,6 @@ public class ServerInfoAdminResource {
|
||||
for (Spi spi : spis) {
|
||||
SpiInfoRepresentation spiRep = new SpiInfoRepresentation();
|
||||
spiRep.setInternal(spi.isInternal());
|
||||
spiRep.setSystemInfo(ServerInfoAwareProviderFactory.class.isAssignableFrom(spi.getProviderFactoryClass()));
|
||||
|
||||
List<String> providerIds = new LinkedList<>(session.listProviderIds(spi.getProviderClass()));
|
||||
Collections.sort(providerIds);
|
||||
@@ -83,8 +90,9 @@ public class ServerInfoAdminResource {
|
||||
if (providerIds != null) {
|
||||
for (String name : providerIds) {
|
||||
ProviderRepresentation provider = new ProviderRepresentation();
|
||||
if (spiRep.isSystemInfo()) {
|
||||
provider.setOperationalInfo(((ServerInfoAwareProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name)).getOperationalInfo());
|
||||
ProviderFactory<?> pi = session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name);
|
||||
if (ServerInfoAwareProviderFactory.class.isAssignableFrom(pi.getClass())) {
|
||||
provider.setOperationalInfo(((ServerInfoAwareProviderFactory) pi).getOperationalInfo());
|
||||
}
|
||||
providers.put(name, provider);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.util.Map;
|
||||
public class SpiInfoRepresentation {
|
||||
|
||||
private boolean internal;
|
||||
private boolean systemInfo;
|
||||
|
||||
private Map<String, ProviderRepresentation> providers;
|
||||
|
||||
@@ -20,14 +19,6 @@ public class SpiInfoRepresentation {
|
||||
this.internal = internal;
|
||||
}
|
||||
|
||||
public boolean isSystemInfo() {
|
||||
return systemInfo;
|
||||
}
|
||||
|
||||
public void setSystemInfo(boolean systemInfo) {
|
||||
this.systemInfo = systemInfo;
|
||||
}
|
||||
|
||||
public Map<String, ProviderRepresentation> getProviders() {
|
||||
return providers;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user