Merge pull request #1805 from velias/KEYCLOAK-2033

KEYCLOAK-2033 - Allow custom SPI Providers to show additional info on
This commit is contained in:
Stian Thorgersen
2015-11-12 09:21:38 +01:00
8 changed files with 110 additions and 59 deletions

View File

@@ -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);

View File

@@ -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> {
}

View File

@@ -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[]{

View File

@@ -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> {
}

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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);
}

View File

@@ -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;
}