keycloak-aplcache
Changes
distribution/appliance-dist/assembly.xml 36(+14 -22)
docbook/reference/en/en-US/modules/providers.xml 276(+276 -0)
examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java 2(+1 -1)
integration/wildfly-extensions/pom.xml 51(+51 -0)
integration/wildfly-extensions/src/main/java/org/keycloak/provider/wildfly/ModuleProviderLoaderFactory.java 33(+33 -0)
integration/wildfly-extensions/src/main/resources/META-INF/services/org.keycloak.provider.ProviderLoaderFactory 1(+1 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java 11(+11 -0)
social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java 6(+4 -2)
social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProviderFactory.java 6(+4 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderModelTest.java 57(+57 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderRegistrationTest.java 105(+105 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java 276(+276 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java 48(+48 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProviderFactory.java 42(+42 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/social/CustomSocialProvider.java 49(+49 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/social/CustomSocialProviderFactory.java 43(+43 -0)
testsuite/integration/src/test/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory 1(+1 -0)
testsuite/integration/src/test/resources/META-INF/services/org.keycloak.social.SocialIdentityProviderFactory 1(+1 -0)
Details
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
index a34438e..652a109 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/AbstractOAuth2IdentityProvider.java
@@ -105,6 +105,11 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
}
}
+ @Override
+ public C getConfig() {
+ return super.getConfig();
+ }
+
protected AuthenticationResponse doHandleResponse(String response) throws IOException {
String token = extractTokenFromResponse(response, OAUTH2_PARAMETER_ACCESS_TOKEN);
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java
index 52be1cd..45aae22 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OAuth2IdentityProviderConfig.java
@@ -19,15 +19,13 @@ package org.keycloak.broker.oidc;
import org.keycloak.models.IdentityProviderModel;
-import java.util.Map;
-
/**
* @author Pedro Igor
*/
public class OAuth2IdentityProviderConfig extends IdentityProviderModel {
- public OAuth2IdentityProviderConfig(String providerId, String id, String name, Map<String, String> config) {
- super(providerId, id, name, config);
+ public OAuth2IdentityProviderConfig(IdentityProviderModel model) {
+ super(model);
}
public String getAuthorizationUrl() {
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
index 302221a..ba2dceb 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderConfig.java
@@ -17,15 +17,15 @@
*/
package org.keycloak.broker.oidc;
-import java.util.Map;
+import org.keycloak.models.IdentityProviderModel;
/**
* @author Pedro Igor
*/
public class OIDCIdentityProviderConfig extends OAuth2IdentityProviderConfig {
- public OIDCIdentityProviderConfig(String providerId, String id, String name, Map<String, String> config) {
- super(providerId, id, name, config);
+ public OIDCIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
+ super(identityProviderModel);
}
public String getPrompt() {
diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
index 10316d9..65bfedd 100644
--- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
+++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProviderFactory.java
@@ -25,6 +25,8 @@ import org.keycloak.models.IdentityProviderModel;
*/
public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory<OIDCIdentityProvider> {
+ public static final String PROVIDER_ID = "oidc";
+
@Override
public String getName() {
return "OpenID Connect v1.0";
@@ -32,11 +34,11 @@ public class OIDCIdentityProviderFactory extends AbstractIdentityProviderFactory
@Override
public OIDCIdentityProvider create(IdentityProviderModel model) {
- return new OIDCIdentityProvider(new OIDCIdentityProviderConfig(getId(), model.getId(), model.getName(), model.getConfig()));
+ return new OIDCIdentityProvider(new OIDCIdentityProviderConfig(model));
}
@Override
public String getId() {
- return "oidc";
+ return PROVIDER_ID;
}
}
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 6ce34fc..c389b1d 100644
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -69,7 +69,6 @@ import java.util.List;
*/
public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityProviderConfig> {
- private static final String SAML_REQUEST_PARAMETER = "SAMLRequest";
private static final String SAML_RESPONSE_PARAMETER = "SAMLResponse";
private static final String RELAY_STATE_PARAMETER = "RelayState";
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
index e6efe16..e4be0eb 100644
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
@@ -19,19 +19,16 @@ package org.keycloak.broker.saml;
import org.keycloak.models.IdentityProviderModel;
-import java.util.Map;
-
/**
* @author Pedro Igor
*/
public class SAMLIdentityProviderConfig extends IdentityProviderModel {
public SAMLIdentityProviderConfig() {
- super();
}
- public SAMLIdentityProviderConfig(String providerId, String id, String name, Map<String, String> config) {
- super(providerId, id, name, config);
+ public SAMLIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
+ super(identityProviderModel);
}
public String getSingleSignOnServiceUrl() {
diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
index ce5faba..9b1e551 100644
--- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
@@ -40,6 +40,8 @@ import java.util.Map;
*/
public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory<SAMLIdentityProvider> {
+ public static final String PROVIDER_ID = "saml";
+
@Override
public String getName() {
return "SAML v2.0";
@@ -47,7 +49,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
@Override
public SAMLIdentityProvider create(IdentityProviderModel model) {
- return new SAMLIdentityProvider(new SAMLIdentityProviderConfig(getId(), model.getId(), model.getName(), model.getConfig()));
+ return new SAMLIdentityProvider(new SAMLIdentityProviderConfig(model));
}
@Override
@@ -121,6 +123,6 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
@Override
public String getId() {
- return "saml";
+ return PROVIDER_ID;
}
}
distribution/appliance-dist/assembly.xml 36(+14 -22)
diff --git a/distribution/appliance-dist/assembly.xml b/distribution/appliance-dist/assembly.xml
index 81efad1..37f59db 100755
--- a/distribution/appliance-dist/assembly.xml
+++ b/distribution/appliance-dist/assembly.xml
@@ -42,7 +42,8 @@
</includes>
</fileSet>
<fileSet>
- <directory>${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF</directory>
+ <directory>${project.build.directory}/unpacked/deployments/auth-server.war/WEB-INF/classes/META-INF
+ </directory>
<outputDirectory>keycloak/standalone/configuration</outputDirectory>
<includes>
<include>keycloak-server.json</include>
@@ -61,27 +62,18 @@
<outputDirectory>keycloak/welcome-content</outputDirectory>
<includes>
<include>*.*</include>
- </includes>
+ </includes>
</fileSet>
</fileSets>
- <!--
- <dependencySets>
- <dependencySet>
- <unpack>false</unpack>
- <useTransitiveDependencies>false</useTransitiveDependencies>
- <includes>
- <include>org.keycloak:keycloak-wildfly-adapter-dist:zip</include>
- <include>org.keycloak:keycloak-as7-adapter-dist:zip</include>
- <include>org.keycloak:keycloak-eap6-adapter-dist:zip</include>
- <include>org.keycloak:keycloak-tomcat6-adapter-dist:zip</include>
- <include>org.keycloak:keycloak-tomcat7-adapter-dist:zip</include>
- <include>org.keycloak:keycloak-tomcat8-adapter-dist:zip</include>
- <include>org.keycloak:keycloak-jetty81-adapter-dist:zip</include>
- <include>org.keycloak:keycloak-jetty91-adapter-dist:zip</include>
- <include>org.keycloak:keycloak-jetty92-adapter-dist:zip</include>
- </includes>
- <outputDirectory>adapters</outputDirectory>
- </dependencySet>
- </dependencySets>
- -->
+
+ <files>
+ <file>
+ <source>src/main/themes/README.txt</source>
+ <outputDirectory>keycloak/standalone/configuration/themes</outputDirectory>
+ </file>
+ <file>
+ <source>src/main/providers/README.txt</source>
+ <outputDirectory>keycloak/standalone/configuration/providers</outputDirectory>
+ </file>
+ </files>
</assembly>
diff --git a/distribution/appliance-dist/src/main/providers/README.txt b/distribution/appliance-dist/src/main/providers/README.txt
new file mode 100644
index 0000000..a6d523b
--- /dev/null
+++ b/distribution/appliance-dist/src/main/providers/README.txt
@@ -0,0 +1,2 @@
+Any provider implementation jars and libraries in this folder will be loaded by Keycloak. See the providers
+section in the documentation for more details.
\ No newline at end of file
diff --git a/distribution/appliance-dist/src/main/themes/README.txt b/distribution/appliance-dist/src/main/themes/README.txt
new file mode 100644
index 0000000..705b73a
--- /dev/null
+++ b/distribution/appliance-dist/src/main/themes/README.txt
@@ -0,0 +1,3 @@
+Themes to configure the look and feel of login pages and account management console. It's not recommended to
+modify existing the built-in themes, instead you should create a new theme that extends a built-in theme. See the theme
+section in the documentation for more details.
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index b5c21ab..0909572 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -4,6 +4,7 @@
<!ENTITY License SYSTEM "modules/License.xml">
<!ENTITY Overview SYSTEM "modules/Overview.xml">
<!ENTITY Installation SYSTEM "modules/server-installation.xml">
+ <!ENTITY Providers SYSTEM "modules/providers.xml">
<!ENTITY OpenShift SYSTEM "modules/openshift.xml">
<!ENTITY AdminPermissions SYSTEM "modules/admin-permissions.xml">
<!ENTITY PerRealmAdminPermissions SYSTEM "modules/per-realm-admin-permissions.xml">
@@ -79,6 +80,7 @@ This one is short
&License;
&Overview;
&Installation;
+ &Providers;
&OpenShift;
&AdminPermissions;
&PerRealmAdminPermissions;
diff --git a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
index c7a263e..58b6b02 100755
--- a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
+++ b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml
@@ -80,7 +80,17 @@
<section>
<title>Version specific migration</title>
<section>
- <title>Migrating from 1.1.Beta1 to 1.1.Beta2</title>
+ <title>Migrating from 1.1.0.Beta2 to 1.1.0.Final</title>
+ <itemizedlist>
+ <listitem>
+ Providers are no longer loaded from <literal>WEB-INF/lib</literal>, they are now loaded
+ from <literal>standalone/configuration/providers</literal>. See the
+ <link linkend='providers'>providers</link> section for more details.
+ </listitem>
+ </itemizedlist>
+ </section>
+ <section>
+ <title>Migrating from 1.1.0.Beta1 to 1.1.0.Beta2</title>
<itemizedlist>
<listitem>Adapters are now a separate download. They are not included in appliance and war distribution. We have too many now and the distro
is getting bloated.</listitem>
@@ -95,7 +105,7 @@
</itemizedlist>
</section>
<section>
- <title>Migrating from 1.0.x.Final to 1.1.Beta1</title>
+ <title>Migrating from 1.0.x.Final to 1.1.0.Beta1</title>
<itemizedlist>
<listitem>RealmModel JPA and Mongo storage schema has changed</listitem>
<listitem>UserSessionModel JPA and Mongo storage schema has changed as these interfaces have been refactored</listitem>
docbook/reference/en/en-US/modules/providers.xml 276(+276 -0)
diff --git a/docbook/reference/en/en-US/modules/providers.xml b/docbook/reference/en/en-US/modules/providers.xml
new file mode 100755
index 0000000..62b5b8e
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/providers.xml
@@ -0,0 +1,276 @@
+<chapter id="providers">
+ <title>Providers and SPIs</title>
+
+ <para>
+ Keycloak is designed to cover most use-cases without requiring custom code, but we also want it to be
+ customizable. To achive this Keycloak has a number of SPIs which you can implement your own providers for.
+ </para>
+
+ <section>
+ <title>Implementing a SPI</title>
+ <para>
+ To implement an SPI you need to implement it's ProviderFactory and Provider interfaces. You also need to
+ create a provider-configuration file. For example to implement the Event Listener SPI you need to implement
+ EventListenerProviderFactory and EventListenerProvider and also provide the file
+ <literal>META-INF/services/org.keycloak.events.EventListenerProviderFactory</literal>
+ </para>
+ <para>
+ For example to implement the Event Listener SPI you start by implementing EventListenerProviderFactory:
+<programlisting><![CDATA[{
+package org.acme.provider;
+
+import ...
+
+public class MyEventListenerProviderFactory implements EventListenerProviderFactory {
+
+ private List<Event> events;
+
+ public String getId() {
+ return "my-event-listener";
+ }
+
+ public void init(Config.Scope config) {
+ int max = config.getInt("max");
+ events = new MaxList(max);
+ }
+
+ public EventListenerProvider create(KeycloakSession session) {
+ return new MyEventListenerProvider(events);
+ }
+
+ public void close() {
+ events = null;
+ }
+
+}
+}]]></programlisting>
+ The example uses a 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
+ instances are created by calling create on the factory for each requests so these should be light-weight.
+ </para>
+ <para>
+ Next you would implement EventListenerProvider:
+<programlisting><![CDATA[{
+package org.acme.provider;
+
+import ...
+
+public class MyEventListenerProvider implements EventListenerProvider {
+
+ private List<Event> events;
+
+ public MyEventListenerProvider(List<Event> events) {
+ this.events = events;
+ }
+
+ @Override
+ public void onEvent(Event event) {
+ events.add(event);
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+}
+}]]></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>
+
+ <section>
+ <title>Registering provider implementations</title>
+ <para>
+ Keycloak loads provider implementations from the file-system. By default all JARs inside
+ <literal>standalone/configuration/providers</literal> are loaded. This is simple, but requires all providers
+ to share the same library. All provides also inherit all classes from the Keycloak class-loader. In the future
+ we'll add support to load providers from modules, which allows better control of class isolation.
+ </para>
+ <para>
+ To register your provider simply copy the JAR including the ProviderFactory and Provider classes and the
+ provider configuration file to <literal>standalone/configuration/providers</literal>.
+ </para>
+ <para>
+ You can also define multiple provider class-path if you want to create isolated class-loaders. To do this
+ edit keycloak-server.json and add more classpath entries to the providers array. For example:
+<programlisting><![CDATA[{
+ "providers": [
+ "classpath:provider1.jar;lib-v1.jar",
+ "classpath:provider2.jar;lib-v2.jar"
+ ]
+}]]></programlisting>
+ The above example will create two separate class-loaders for providers. The classpath entries follow the
+ same syntax as Java classpath, with ';' separating multiple-entries. Wildcard is also supported allowing
+ loading all jars (files with .jar or .JAR extension) in a folder, for example:
+<programlisting><![CDATA[{
+ "providers": [
+ "classpath:/home/user/providers/*"
+ ]
+}]]></programlisting>
+ </para>
+ </section>
+
+ <section>
+ <title>Available SPIs</title>
+ <para>
+ Here's a list of the available SPIs and a brief description. For more details on each SPI refer to
+ individual
+ sections.
+ <variablelist>
+ <varlistentry>
+ <term>Account</term>
+ <listitem>
+ Provides the account manage console pages. The default implementation uses FreeMarker templates.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Connections Infinispan</term>
+ <listitem>
+ Loads and configures Infinispan connections. The default implementation can load connections
+ from
+ the Infinispan subsystem, or alternatively can be manually configured in keycloak-server.json.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Connections Jpa</term>
+ <listitem>
+ Loads and configures Infinispan connections. The default implementation can load datasources
+ from
+ WildFly/EAP, or alternatively can be manually configured in keycloak-server.json.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Connections Jpa Updater</term>
+ <listitem>
+ Updates database schema. The default implementation uses Liquibase.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Connections Mongo</term>
+ <listitem>
+ Loads and configures MongoDB connections. The default implementation is configured in
+ keycloak-server.json.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Email</term>
+ <listitem>
+ Formats and sends email. The default implementation uses FreeMarker templates and JavaMail.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Events Listener</term>
+ <listitem>
+ Listen to user related events for example user login success and failures. Keycloak provides two
+ implementations out of box. One that logs events to the server log and another that can send
+ email
+ notifications to users on certain events.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Events Store</term>
+ <listitem>
+ Store user related events so they can be viewed through the admin console and account management
+ console.
+ Keycloak provides implementations for Relational Databases and MongoDB.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Export</term>
+ <listitem>
+ Exports the Keycloak database. Keycloak provides implementations that export to JSON files
+ either
+ as a single file, multiple file in a directory or a encrypted ZIP archive.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Import</term>
+ <listitem>
+ Imports and exported Keycloak database. Keycloak provides implementations that import from JSON
+ files either
+ as a single file, multiple file in a directory or a encrypted ZIP archive.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Login</term>
+ <listitem>
+ Provides the login pages. The default implementation uses FreeMarker templates.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Login Protocol</term>
+ <listitem>
+ Provides protocols. Keycloak provides implementations of OpenID Connect and SAML 2.0.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Realm</term>
+ <listitem>
+ Provides realm and application meta-data. Keycloak provides implementations for Relational
+ Databases
+ and MongoDB.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Realm Cache</term>
+ <listitem>
+ Caches realm and application meta-data to improve performance. Keycloak provides a basic
+ in-memory
+ cache and a Infinispan cache.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Theme</term>
+ <listitem>
+ Allows creating themes to customize look and feel. Keycloak provides implementations that can
+ load
+ themes from the file-system or classpath.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Timer</term>
+ <listitem>
+ Executes scheduled tasks. Keycloak provides a basic implementation based on java.util.Timer.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>User</term>
+ <listitem>
+ Provides users and role-mappings. Keycloak provides implementations for Relational Databases
+ and MongoDB.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>User Cache</term>
+ <listitem>
+ Caches users and role-mappings to improve performance. Keycloak provides a basic in-memory
+ cache and a Infinispan cache.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>User Federation</term>
+ <listitem>
+ Support syncing users from an external source. Keycloak provides implementations for LDAP and
+ Active Directory.
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>User Sessions</term>
+ <listitem>
+ Provides users session information. Keycloak provides implementations for basic in-memory,
+ Infinispan,
+ Relational Databases and MongoDB
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </section>
+</chapter>
diff --git a/docbook/reference/en/en-US/modules/user-federation.xml b/docbook/reference/en/en-US/modules/user-federation.xml
index 5eb1524..f1ba3ba 100755
--- a/docbook/reference/en/en-US/modules/user-federation.xml
+++ b/docbook/reference/en/en-US/modules/user-federation.xml
@@ -173,8 +173,8 @@
contain a file called <literal>org.keycloak.models.UserFederationProviderFactory</literal>
within the <literal>META-INF/services</literal> directory of the JAR. This file is a list
of fully qualified classnames of all implementations of <literal>UserFederationProviderFactory</literal>.
- This is how Keycloak discovers which providers have been deployed. Place the JAR in the
- keycloak WAR deployment in the <literal>WEB-INF/lib</literal> directory.
+ For more details on writing provider implementations and how to deploy to Keycloak refer to the
+ <link linkend='providers'>providers</link> section.
</para>
</section>
diff --git a/examples/providers/event-listener-sysout/README.md b/examples/providers/event-listener-sysout/README.md
index 1d762d7..3325add 100644
--- a/examples/providers/event-listener-sysout/README.md
+++ b/examples/providers/event-listener-sysout/README.md
@@ -1,4 +1,7 @@
Example Event Listener that prints events to System.out
=======================================================
-To deploy copy target/event-listener-sysout-example.jar to standalone/deployments/auth-server.war/WEB-INF/lib. Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events, followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and login again to see events printed to System.out.
+To deploy copy target/event-listener-sysout-example.jar to standalone/configuration/providers.
+Then start (or restart) the server. Once started open the admin console, select your realm, then click on Events,
+followed by config. Click on Listeners select box, then pick sysout from the dropdown. After this try to logout and
+login again to see events printed to System.out.
diff --git a/examples/providers/event-store-mem/README.md b/examples/providers/event-store-mem/README.md
index 2999bd8..c1762c0 100644
--- a/examples/providers/event-store-mem/README.md
+++ b/examples/providers/event-store-mem/README.md
@@ -1,7 +1,9 @@
Example Event Store that stores events in memory
================================================
-To deploy copy target/event-store-mem-example.jar to standalone/deployments/auth-server.war/WEB-INF/lib. Then edit standalone/configuration/keycloak-server.json, change:
+To deploy copy target/event-store-mem-example.jar to standalone/configuration/providers.
+
+Then edit standalone/configuration/keycloak-server.json, change:
"eventsStore": {
"provider": "jpa"
@@ -13,4 +15,6 @@ to:
"provider": "in-mem"
}
-Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events, followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab again in the admin console to view events from the in-mem provider.
+Then start (or restart)the server. Once started open the admin console, select your realm, then click on Events,
+followed by config. Set the toggle for Enabled to ON. After this try to logout and login again then open the Events tab
+again in the admin console to view events from the in-mem provider.
diff --git a/examples/providers/federation-provider/README.md b/examples/providers/federation-provider/README.md
index b908fe7..9a25a2e 100755
--- a/examples/providers/federation-provider/README.md
+++ b/examples/providers/federation-provider/README.md
@@ -2,8 +2,8 @@ Example User Federation Provider
===================================================
This is an example of user federation backed by a simple properties file. This properties file only contains username/password
-key pairs. To deploy, build this directory then take the jar and copy it to the WEB-INF/lib of the keycloak server's
-WAR file. You will then have to restart the authentication server.
+key pairs. To deploy, build this directory then take the jar and copy it to standalone/configuration/providers.
+You will then have to restart the authentication server.
The ClasspathPropertiesFederationProvider is an example of a readonly provider. If you go to the Users/Federation
page of the admin console you will see this provider listed under "classpath-properties. To configure this provider you
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
index f06250a..49ad517 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
@@ -20,7 +20,7 @@ public class ClasspathPropertiesFederationFactory extends BasePropertiesFederati
}
protected InputStream getPropertiesFileStream(String path) {
- InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
+ InputStream is = getClass().getClassLoader().getResourceAsStream(path);
if (is == null) {
throw new IllegalStateException("Path not found for properties file");
integration/wildfly-extensions/pom.xml 51(+51 -0)
diff --git a/integration/wildfly-extensions/pom.xml b/integration/wildfly-extensions/pom.xml
new file mode 100755
index 0000000..e627d32
--- /dev/null
+++ b/integration/wildfly-extensions/pom.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ Copyright 2013 JBoss Inc
+~
+~ 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.
+-->
+<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">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-parent</artifactId>
+ <version>1.2.0.Beta1-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-wildfly-extensions</artifactId>
+ <name>Keycloak WildFly Extensions</name>
+ <description/>
+ <packaging>jar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly.core</groupId>
+ <artifactId>wildfly-controller</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-services</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/integration/wildfly-extensions/src/main/java/org/keycloak/provider/wildfly/ModuleProviderLoaderFactory.java b/integration/wildfly-extensions/src/main/java/org/keycloak/provider/wildfly/ModuleProviderLoaderFactory.java
new file mode 100644
index 0000000..cb4a6f1
--- /dev/null
+++ b/integration/wildfly-extensions/src/main/java/org/keycloak/provider/wildfly/ModuleProviderLoaderFactory.java
@@ -0,0 +1,33 @@
+package org.keycloak.provider.wildfly;
+
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleClassLoader;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoadException;
+import org.keycloak.provider.DefaultProviderLoader;
+import org.keycloak.provider.ProviderLoader;
+import org.keycloak.provider.ProviderLoaderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ModuleProviderLoaderFactory implements ProviderLoaderFactory {
+
+ @Override
+ public boolean supports(String type) {
+ return "module".equals(type);
+ }
+
+ @Override
+ public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
+ try {
+ System.out.println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx");
+ Module module = Module.getContextModuleLoader().loadModule(ModuleIdentifier.fromString(resource));
+ ModuleClassLoader classLoader = module.getClassLoader();
+ return new DefaultProviderLoader(classLoader);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/integration/wildfly-extensions/src/main/resources/META-INF/services/org.keycloak.provider.ProviderLoaderFactory b/integration/wildfly-extensions/src/main/resources/META-INF/services/org.keycloak.provider.ProviderLoaderFactory
new file mode 100644
index 0000000..52aec1f
--- /dev/null
+++ b/integration/wildfly-extensions/src/main/resources/META-INF/services/org.keycloak.provider.ProviderLoaderFactory
@@ -0,0 +1 @@
+org.keycloak.provider.wildfly.ModuleProviderLoaderFactory
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
index 457ba35..12501bb 100644
--- a/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/IdentityProviderModel.java
@@ -55,17 +55,15 @@ public class IdentityProviderModel {
private Map<String, String> config = new HashMap<String, String>();
public IdentityProviderModel() {
- this(null, null, null, null);
}
- public IdentityProviderModel(String providerId, String id, String name, Map<String, String> config) {
- this.providerId = providerId;
- this.id = id;
- this.name = name;
-
- if (config != null) {
- this.config.putAll(config);
- }
+ public IdentityProviderModel(IdentityProviderModel model) {
+ this.providerId = model.getProviderId();
+ this.id = model.getId();
+ this.name = model.getName();
+ this.config = new HashMap<String, String>(model.getConfig());
+ this.enabled = model.isEnabled();
+ this.updateProfileFirstLogin = model.isUpdateProfileFirstLogin();
}
public String getId() {
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 454179e..4ce7d61 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -164,6 +164,7 @@ public interface RealmModel extends RoleContainerModel {
void setSmtpConfig(Map<String, String> smtpConfig);
List<IdentityProviderModel> getIdentityProviders();
+ IdentityProviderModel getIdentityProviderById(String identityProviderId);
void addIdentityProvider(IdentityProviderModel identityProvider);
void removeIdentityProviderById(String providerId);
void updateIdentityProvider(IdentityProviderModel identityProvider);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 23da5c5..aede6a2 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -8,6 +8,7 @@ import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
+import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
@@ -21,6 +22,7 @@ import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.ClaimRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OAuthClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
@@ -229,6 +231,21 @@ public class RepresentationToModel {
UserModel user = createUser(session, newRealm, userRep, appMap);
}
}
+
+ if (rep.getIdentityProviders() != null) {
+ for (IdentityProviderRepresentation identityProviderRepresentation : rep.getIdentityProviders()) {
+ IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+
+ identityProviderModel.setId(identityProviderRepresentation.getId());
+ identityProviderModel.setProviderId(identityProviderRepresentation.getProviderId());
+ identityProviderModel.setName(identityProviderRepresentation.getName());
+ identityProviderModel.setEnabled(identityProviderRepresentation.isEnabled());
+ identityProviderModel.setUpdateProfileFirstLogin(identityProviderRepresentation.isUpdateProfileFirstLogin());
+ identityProviderModel.setConfig(identityProviderRepresentation.getConfig());
+
+ newRealm.addIdentityProvider(identityProviderModel);
+ }
+ }
}
public static void updateRealm(RealmRepresentation rep, RealmModel realm) {
@@ -730,5 +747,4 @@ public class RepresentationToModel {
}
}
-
}
diff --git a/model/api/src/main/java/org/keycloak/provider/ProviderLoader.java b/model/api/src/main/java/org/keycloak/provider/ProviderLoader.java
new file mode 100644
index 0000000..79d78b0
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/provider/ProviderLoader.java
@@ -0,0 +1,12 @@
+package org.keycloak.provider;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderLoader {
+
+ List<ProviderFactory> load(Spi spi);
+
+}
diff --git a/model/api/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java b/model/api/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
new file mode 100644
index 0000000..a76955c
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/provider/ProviderLoaderFactory.java
@@ -0,0 +1,12 @@
+package org.keycloak.provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface ProviderLoaderFactory {
+
+ boolean supports(String type);
+
+ ProviderLoader create(ClassLoader baseClassLoader, String resource);
+
+}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 98e120c..dc4ede4 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -616,6 +616,17 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public IdentityProviderModel getIdentityProviderById(String identityProviderId) {
+ for (IdentityProviderModel identityProviderModel : getIdentityProviders()) {
+ if (identityProviderModel.getId().equals(identityProviderId)) {
+ return identityProviderModel;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public void addIdentityProvider(IdentityProviderModel identityProvider) {
getDelegateForUpdate();
updated.addIdentityProvider(identityProvider);
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 b8d864c..af73db6 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
@@ -1111,9 +1111,12 @@ public class RealmAdapter implements RealmModel {
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
for (IdentityProviderEntity entity: realm.getIdentityProviders()) {
- IdentityProviderModel identityProviderModel = new IdentityProviderModel(entity.getProviderId(), entity.getId(), entity.getName(),
- entity.getConfig());
+ IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+ identityProviderModel.setProviderId(entity.getProviderId());
+ identityProviderModel.setId(entity.getId());
+ identityProviderModel.setName(entity.getName());
+ identityProviderModel.setConfig(entity.getConfig());
identityProviderModel.setEnabled(entity.isEnabled());
identityProviderModel.setUpdateProfileFirstLogin(entity.isUpdateProfileFirstLogin());
@@ -1124,6 +1127,17 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public IdentityProviderModel getIdentityProviderById(String identityProviderId) {
+ for (IdentityProviderModel identityProviderModel : getIdentityProviders()) {
+ if (identityProviderModel.getId().equals(identityProviderId)) {
+ return identityProviderModel;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public void addIdentityProvider(IdentityProviderModel identityProvider) {
IdentityProviderEntity entity = new IdentityProviderEntity();
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 8c32349..3bb7113 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
@@ -787,9 +787,12 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
for (IdentityProviderEntity entity: realm.getIdentityProviders()) {
- IdentityProviderModel identityProviderModel = new IdentityProviderModel(entity.getProviderId(), entity.getId(), entity.getName(),
- entity.getConfig());
+ IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+ identityProviderModel.setProviderId(entity.getProviderId());
+ identityProviderModel.setId(entity.getId());
+ identityProviderModel.setName(entity.getName());
+ identityProviderModel.setConfig(entity.getConfig());
identityProviderModel.setEnabled(entity.isEnabled());
identityProviderModel.setUpdateProfileFirstLogin(entity.isUpdateProfileFirstLogin());
@@ -800,6 +803,17 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
}
@Override
+ public IdentityProviderModel getIdentityProviderById(String identityProviderId) {
+ for (IdentityProviderModel identityProviderModel : getIdentityProviders()) {
+ if (identityProviderModel.getId().equals(identityProviderId)) {
+ return identityProviderModel;
+ }
+ }
+
+ return null;
+ }
+
+ @Override
public void addIdentityProvider(IdentityProviderModel identityProvider) {
IdentityProviderEntity entity = new IdentityProviderEntity();
diff --git a/server/src/main/resources/META-INF/keycloak-server.json b/server/src/main/resources/META-INF/keycloak-server.json
index 9f0d03e..1f92932 100755
--- a/server/src/main/resources/META-INF/keycloak-server.json
+++ b/server/src/main/resources/META-INF/keycloak-server.json
@@ -1,4 +1,8 @@
{
+ "providers": [
+ "classpath:${jboss.server.config.dir}/providers/*"
+ ],
+
"admin": {
"realm": "master"
},
diff --git a/services/src/main/java/org/keycloak/provider/DefaultProviderLoader.java b/services/src/main/java/org/keycloak/provider/DefaultProviderLoader.java
new file mode 100644
index 0000000..6359969
--- /dev/null
+++ b/services/src/main/java/org/keycloak/provider/DefaultProviderLoader.java
@@ -0,0 +1,27 @@
+package org.keycloak.provider;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ServiceLoader;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultProviderLoader implements ProviderLoader {
+
+ private ClassLoader classLoader;
+
+ public DefaultProviderLoader(ClassLoader classLoader) {
+ this.classLoader = classLoader;
+ }
+
+ @Override
+ public List<ProviderFactory> load(Spi spi) {
+ LinkedList<ProviderFactory> list = new LinkedList<ProviderFactory>();
+ for (ProviderFactory f : ServiceLoader.load(spi.getProviderFactoryClass(), classLoader)) {
+ list.add(f);
+ }
+ return list;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/provider/DefaultProviderLoaderFactory.java b/services/src/main/java/org/keycloak/provider/DefaultProviderLoaderFactory.java
new file mode 100644
index 0000000..4985da4
--- /dev/null
+++ b/services/src/main/java/org/keycloak/provider/DefaultProviderLoaderFactory.java
@@ -0,0 +1,18 @@
+package org.keycloak.provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultProviderLoaderFactory implements ProviderLoaderFactory {
+
+ @Override
+ public boolean supports(String type) {
+ return false;
+ }
+
+ @Override
+ public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
+ return new DefaultProviderLoader(baseClassLoader);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/provider/FileSystemProviderLoaderFactory.java b/services/src/main/java/org/keycloak/provider/FileSystemProviderLoaderFactory.java
new file mode 100644
index 0000000..4c887eb
--- /dev/null
+++ b/services/src/main/java/org/keycloak/provider/FileSystemProviderLoaderFactory.java
@@ -0,0 +1,63 @@
+package org.keycloak.provider;
+
+import org.jboss.logging.Logger;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FileSystemProviderLoaderFactory implements ProviderLoaderFactory {
+
+ private static final Logger log = Logger.getLogger(FileSystemProviderLoaderFactory.class);
+
+ @Override
+ public boolean supports(String type) {
+ return "classpath".equals(type);
+ }
+
+ @Override
+ public ProviderLoader create(ClassLoader baseClassLoader, String resource) {
+ return new DefaultProviderLoader(createClassLoader(baseClassLoader, resource.split(";")));
+ }
+
+ private static URLClassLoader createClassLoader(ClassLoader parent, String... files) {
+ try {
+ List<URL> urls = new LinkedList<URL>();
+
+ for (String f : files) {
+ if (f.endsWith("*")) {
+ File dir = new File(f.substring(0, f.length() - 1));
+ if (dir.isDirectory()) {
+ for (File file : dir.listFiles(new JarFilter())) {
+ urls.add(file.toURI().toURL());
+ }
+ }
+ } else {
+ urls.add(new File(f).toURI().toURL());
+ }
+ }
+
+ log.debug("Loading providers from " + urls.toString());
+
+ return new URLClassLoader(urls.toArray(new URL[urls.size()]), parent);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class JarFilter implements FilenameFilter {
+
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().endsWith(".jar");
+ }
+
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/provider/ProviderManager.java b/services/src/main/java/org/keycloak/provider/ProviderManager.java
new file mode 100644
index 0000000..6a301fd
--- /dev/null
+++ b/services/src/main/java/org/keycloak/provider/ProviderManager.java
@@ -0,0 +1,74 @@
+package org.keycloak.provider;
+
+import org.jboss.logging.Logger;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ProviderManager {
+
+ private static final Logger log = Logger.getLogger(ProviderManager.class);
+
+ private List<ProviderLoader> loaders = new LinkedList<ProviderLoader>();
+ private Map<String, List<ProviderFactory>> cache = new HashMap<String, List<ProviderFactory>>();
+
+ public ProviderManager(ClassLoader baseClassLoader, String... resources) {
+ List<ProviderLoaderFactory> factories = new LinkedList<ProviderLoaderFactory>();
+ for (ProviderLoaderFactory f : ServiceLoader.load(ProviderLoaderFactory.class)) {
+ factories.add(f);
+ }
+
+ log.debugv("Provider loaders {0}", factories);
+
+ loaders.add(new DefaultProviderLoader(baseClassLoader));
+
+ if (resources != null) {
+ for (String r : resources) {
+ String type = r.substring(0, r.indexOf(':'));
+ String resource = r.substring(r.indexOf(':') + 1, r.length());
+
+ boolean found = false;
+ for (ProviderLoaderFactory f : factories) {
+ if (f.supports(type)) {
+ loaders.add(f.create(baseClassLoader, resource));
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new RuntimeException("Provider loader for " + r + " not found");
+ }
+ }
+ }
+ }
+
+ public synchronized List<ProviderFactory> load(Spi spi) {
+ List<ProviderFactory> factories = cache.get(spi.getName());
+ if (factories == null) {
+ factories = new LinkedList<ProviderFactory>();
+ for (ProviderLoader loader : loaders) {
+ List<ProviderFactory> f = loader.load(spi);
+ if (f != null) {
+ factories.addAll(f);
+ }
+ }
+ }
+ return factories;
+ }
+
+ public synchronized ProviderFactory load(Spi spi, String providerId) {
+ for (ProviderFactory f : load(spi)) {
+ if (f.getId().equals(providerId)) {
+ return f;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index fbee2e9..ef3b176 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -6,6 +6,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi;
import java.util.HashMap;
@@ -24,6 +25,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
private Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<Class<? extends Provider>, Map<String, ProviderFactory>>();
public void init() {
+ ProviderManager pm = new ProviderManager(getClass().getClassLoader(), Config.scope().getArray("providers"));
+
for (Spi spi : ServiceLoader.load(Spi.class)) {
Map<String, ProviderFactory> factories = new HashMap<String, ProviderFactory>();
factoriesMap.put(spi.getProviderClass(), factories);
@@ -32,7 +35,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
if (provider != null) {
this.provider.put(spi.getProviderClass(), provider);
- ProviderFactory factory = loadProviderFactory(spi, provider);
+ ProviderFactory factory = pm.load(spi, provider);
+ if (factory == null) {
+ throw new RuntimeException("Failed to find provider " + provider + " for " + spi.getName());
+ }
+
Config.Scope scope = Config.scope(spi.getName(), provider);
factory.init(scope);
@@ -40,7 +47,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
log.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider);
} else {
- for (ProviderFactory factory : ServiceLoader.load(spi.getProviderFactoryClass())) {
+ for (ProviderFactory factory : pm.load(spi)) {
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
factory.init(scope);
@@ -59,15 +66,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
}
}
- private ProviderFactory loadProviderFactory(Spi spi, String id) {
- for (ProviderFactory factory : ServiceLoader.load(spi.getProviderFactoryClass())) {
- if (factory.getId().equals(id)){
- return factory;
- }
- }
- throw new RuntimeException("Failed to find provider " + id + " for " + spi.getName());
- }
-
public KeycloakSession create() {
return new DefaultKeycloakSession(this);
}
diff --git a/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java b/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
index 19ff363..0f14b30 100644
--- a/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/AuthenticationBrokerResource.java
@@ -314,16 +314,16 @@ public class AuthenticationBrokerResource {
}
private IdentityProvider getIdentityProvider(RealmModel realm, String providerId) {
- for (IdentityProviderModel model : realm.getIdentityProviders()) {
- if (model.getId().equals(providerId)) {
- IdentityProviderFactory providerFactory = getIdentityProviderFactory(model);
+ IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(providerId);
- if (providerFactory == null) {
- throw new RuntimeException("Could not find provider factory for identity provider [" + providerId + "].");
- }
+ if (identityProviderModel != null) {
+ IdentityProviderFactory providerFactory = getIdentityProviderFactory(identityProviderModel);
- return providerFactory.create(model);
+ if (providerFactory == null) {
+ throw new RuntimeException("Could not find provider factory for identity provider [" + providerId + "].");
}
+
+ return providerFactory.create(identityProviderModel);
}
return null;
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.ProviderLoaderFactory b/services/src/main/resources/META-INF/services/org.keycloak.provider.ProviderLoaderFactory
new file mode 100644
index 0000000..724fe5f
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.ProviderLoaderFactory
@@ -0,0 +1,2 @@
+org.keycloak.provider.DefaultProviderLoaderFactory
+org.keycloak.provider.FileSystemProviderLoaderFactory
\ No newline at end of file
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
index 518b1e1..774f306 100755
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java
@@ -12,11 +12,10 @@ import org.keycloak.social.SocialIdentityProvider;
*/
public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
- private static final String ID = "facebook";
- private static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize";
- private static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token";
- private static final String PROFILE_URL = "https://graph.facebook.com/me";
- private static final String DEFAULT_SCOPE = "email";
+ public static final String AUTH_URL = "https://graph.facebook.com/oauth/authorize";
+ public static final String TOKEN_URL = "https://graph.facebook.com/oauth/access_token";
+ public static final String PROFILE_URL = "https://graph.facebook.com/me";
+ public static final String DEFAULT_SCOPE = "email";
public FacebookIdentityProvider(OAuth2IdentityProviderConfig config) {
super(config);
diff --git a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java
index c5bdad3..57f484e 100644
--- a/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java
+++ b/social/facebook/src/main/java/org/keycloak/social/facebook/FacebookIdentityProviderFactory.java
@@ -27,6 +27,8 @@ import org.keycloak.social.SocialIdentityProviderFactory;
*/
public class FacebookIdentityProviderFactory extends AbstractIdentityProviderFactory<FacebookIdentityProvider> implements SocialIdentityProviderFactory<FacebookIdentityProvider> {
+ public static final String PROVIDER_ID = "facebook";
+
@Override
public String getName() {
return "Facebook";
@@ -34,11 +36,11 @@ public class FacebookIdentityProviderFactory extends AbstractIdentityProviderFac
@Override
public FacebookIdentityProvider create(IdentityProviderModel model) {
- return new FacebookIdentityProvider(new OAuth2IdentityProviderConfig(getId(), model.getId(), model.getName(), model.getConfig()));
+ return new FacebookIdentityProvider(new OAuth2IdentityProviderConfig(model));
}
@Override
public String getId() {
- return "facebook";
+ return PROVIDER_ID;
}
}
diff --git a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
index b48b825..b24d3ea 100755
--- a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
+++ b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java
@@ -12,11 +12,10 @@ import org.keycloak.social.SocialIdentityProvider;
*/
public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider implements SocialIdentityProvider {
- private static final String ID = "github";
- private static final String AUTH_URL = "https://github.com/login/oauth/authorize";
- private static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
- private static final String PROFILE_URL = "https://api.github.com/user";
- private static final String DEFAULT_SCOPE = "user:email";
+ public static final String AUTH_URL = "https://github.com/login/oauth/authorize";
+ public static final String TOKEN_URL = "https://github.com/login/oauth/access_token";
+ public static final String PROFILE_URL = "https://api.github.com/user";
+ public static final String DEFAULT_SCOPE = "user:email";
public GitHubIdentityProvider(OAuth2IdentityProviderConfig config) {
super(config);
diff --git a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProviderFactory.java b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProviderFactory.java
index 43cea68..ed06598 100644
--- a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProviderFactory.java
+++ b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProviderFactory.java
@@ -27,6 +27,8 @@ import org.keycloak.social.SocialIdentityProviderFactory;
*/
public class GitHubIdentityProviderFactory extends AbstractIdentityProviderFactory<GitHubIdentityProvider> implements SocialIdentityProviderFactory<GitHubIdentityProvider> {
+ public static final String PROVIDER_ID = "github";
+
@Override
public String getName() {
return "GitHub";
@@ -34,11 +36,11 @@ public class GitHubIdentityProviderFactory extends AbstractIdentityProviderFacto
@Override
public GitHubIdentityProvider create(IdentityProviderModel model) {
- return new GitHubIdentityProvider(new OAuth2IdentityProviderConfig(getId(), model.getId(), model.getName(), model.getConfig()));
+ return new GitHubIdentityProvider(new OAuth2IdentityProviderConfig(model));
}
@Override
public String getId() {
- return "github";
+ return PROVIDER_ID;
}
}
diff --git a/social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java b/social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java
index f6f28b5..65a6b12 100755
--- a/social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java
+++ b/social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProvider.java
@@ -30,11 +30,10 @@ import org.keycloak.social.SocialIdentityProvider;
*/
public class GoogleIdentityProvider extends OIDCIdentityProvider implements SocialIdentityProvider<OIDCIdentityProviderConfig> {
- private static final String ID = "google";
- private static final String AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
- private static final String TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
- private static final String PROFILE_URL = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
- private static final String DEFAULT_SCOPE = "openid profile email";
+ public static final String AUTH_URL = "https://accounts.google.com/o/oauth2/auth";
+ public static final String TOKEN_URL = "https://accounts.google.com/o/oauth2/token";
+ public static final String PROFILE_URL = "https://www.googleapis.com/plus/v1/people/me/openIdConnect";
+ public static final String DEFAULT_SCOPE = "openid profile email";
public GoogleIdentityProvider(OIDCIdentityProviderConfig config) {
super(config);
diff --git a/social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProviderFactory.java b/social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProviderFactory.java
index df5d45e..5171c4f 100644
--- a/social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProviderFactory.java
+++ b/social/google/src/main/java/org/keycloak/social/google/GoogleIdentityProviderFactory.java
@@ -27,6 +27,8 @@ import org.keycloak.social.SocialIdentityProviderFactory;
*/
public class GoogleIdentityProviderFactory extends AbstractIdentityProviderFactory<GoogleIdentityProvider> implements SocialIdentityProviderFactory<GoogleIdentityProvider> {
+ public static final String PROVIDER_ID = "google";
+
@Override
public String getName() {
return "Google";
@@ -34,11 +36,11 @@ public class GoogleIdentityProviderFactory extends AbstractIdentityProviderFacto
@Override
public GoogleIdentityProvider create(IdentityProviderModel model) {
- return new GoogleIdentityProvider(new OIDCIdentityProviderConfig(getId(), model.getId(), model.getName(), model.getConfig()));
+ return new GoogleIdentityProvider(new OIDCIdentityProviderConfig(model));
}
@Override
public String getId() {
- return "google";
+ return PROVIDER_ID;
}
}
diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProviderFactory.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProviderFactory.java
index 382ecf9..21730c5 100644
--- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProviderFactory.java
+++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProviderFactory.java
@@ -27,6 +27,8 @@ import org.keycloak.social.SocialIdentityProviderFactory;
*/
public class TwitterIdentityProviderFactory extends AbstractIdentityProviderFactory<TwitterIdentityProvider> implements SocialIdentityProviderFactory<TwitterIdentityProvider> {
+ public static final String PROVIDER_ID = "twitter";
+
@Override
public String getName() {
return "Twitter";
@@ -34,11 +36,11 @@ public class TwitterIdentityProviderFactory extends AbstractIdentityProviderFact
@Override
public TwitterIdentityProvider create(IdentityProviderModel model) {
- return new TwitterIdentityProvider(new OAuth2IdentityProviderConfig(getId(), model.getId(), model.getName(), model.getConfig()));
+ return new TwitterIdentityProvider(new OAuth2IdentityProviderConfig(model));
}
@Override
public String getId() {
- return "twitter";
+ return PROVIDER_ID;
}
}
diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties
index 778d39a..6a329fd 100755
--- a/testsuite/integration/src/main/resources/log4j.properties
+++ b/testsuite/integration/src/main/resources/log4j.properties
@@ -8,6 +8,8 @@ log4j.logger.org.keycloak=info
# Enable to view loaded SPI and Providers
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
+# log4j.logger.org.keycloak.provider.ProviderManager=debug
+# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
# Enable to view database updates
# log4j.logger.org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider=debug
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderModelTest.java
new file mode 100644
index 0000000..2dd3164
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderModelTest.java
@@ -0,0 +1,57 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.testsuite.broker;
+
+import org.junit.Before;
+import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
+import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
+import org.keycloak.social.facebook.FacebookIdentityProviderFactory;
+import org.keycloak.social.github.GitHubIdentityProviderFactory;
+import org.keycloak.social.google.GoogleIdentityProviderFactory;
+import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
+import org.keycloak.testsuite.model.AbstractModelTest;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author pedroigor
+ */
+public abstract class AbstractIdentityProviderModelTest extends AbstractModelTest {
+
+ private Set<String> expectedProviders;
+
+ @Before
+ public void onBefore() {
+ this.expectedProviders = new HashSet<String>();
+
+ this.expectedProviders.add(SAMLIdentityProviderFactory.PROVIDER_ID);
+ this.expectedProviders.add(OIDCIdentityProviderFactory.PROVIDER_ID);
+ this.expectedProviders.add(GoogleIdentityProviderFactory.PROVIDER_ID);
+ this.expectedProviders.add(FacebookIdentityProviderFactory.PROVIDER_ID);
+ this.expectedProviders.add(GitHubIdentityProviderFactory.PROVIDER_ID);
+ this.expectedProviders.add(TwitterIdentityProviderFactory.PROVIDER_ID);
+
+ this.expectedProviders = Collections.unmodifiableSet(this.expectedProviders);
+ }
+
+ protected Set<String> getExpectedProviders() {
+ return this.expectedProviders;
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderRegistrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderRegistrationTest.java
new file mode 100644
index 0000000..0320a0a
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderRegistrationTest.java
@@ -0,0 +1,105 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.testsuite.broker;
+
+import org.junit.Test;
+import org.keycloak.broker.provider.IdentityProvider;
+import org.keycloak.broker.provider.IdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.social.SocialIdentityProvider;
+import org.keycloak.social.SocialIdentityProviderFactory;
+import org.keycloak.testsuite.broker.provider.CustomIdentityProvider;
+import org.keycloak.testsuite.broker.social.CustomSocialProvider;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author pedroigor
+ */
+public class IdentityProviderRegistrationTest extends AbstractIdentityProviderModelTest {
+
+ @Test
+ public void testIdentityProviderRegistration() {
+ Set<String> installedProviders = getInstalledProviders();
+
+ for (String providerId : getExpectedProviders()) {
+ if (!installedProviders.contains(providerId)) {
+ fail("Provider [" + providerId + "] not installed ");
+ }
+ }
+ }
+
+ @Test
+ public void testCustomSocialProviderRegistration() {
+ String providerId = "custom-social-provider";
+
+ assertTrue(getInstalledProviders().contains(providerId));
+
+ SocialIdentityProviderFactory<CustomSocialProvider> providerFactory = (SocialIdentityProviderFactory) this.session.getKeycloakSessionFactory().getProviderFactory(SocialIdentityProvider.class, providerId);
+
+ assertNotNull(providerFactory);
+
+ IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+
+ identityProviderModel.setId("custom-provider");
+
+ CustomSocialProvider customSocialProvider = providerFactory.create(identityProviderModel);
+
+ assertNotNull(customSocialProvider);
+ IdentityProviderModel config = customSocialProvider.getConfig();
+
+ assertNotNull(config);
+ assertEquals("custom-provider", config.getId());
+ }
+
+ @Test
+ public void testCustomIdentityProviderRegistration() {
+ String providerId = "custom-identity-provider";
+
+ assertTrue(getInstalledProviders().contains(providerId));
+
+ IdentityProviderFactory<CustomIdentityProvider> providerFactory = (IdentityProviderFactory) this.session.getKeycloakSessionFactory().getProviderFactory(IdentityProvider.class, providerId);
+
+ assertNotNull(providerFactory);
+
+ IdentityProviderModel identityProviderModel = new IdentityProviderModel();
+
+ identityProviderModel.setId("custom-provider");
+
+ CustomIdentityProvider provider = providerFactory.create(identityProviderModel);
+
+ assertNotNull(provider);
+ IdentityProviderModel config = provider.getConfig();
+
+ assertNotNull(config);
+ assertEquals("custom-provider", config.getId());
+ }
+
+ private Set<String> getInstalledProviders() {
+ Set<String> installedProviders = this.session.listProviderIds(IdentityProvider.class);
+
+ installedProviders.addAll(this.session.listProviderIds(SocialIdentityProvider.class));
+
+ return installedProviders;
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
new file mode 100644
index 0000000..3b011cc
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -0,0 +1,276 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.testsuite.broker;
+
+import org.junit.Test;
+import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig;
+import org.keycloak.broker.oidc.OIDCIdentityProvider;
+import org.keycloak.broker.oidc.OIDCIdentityProviderConfig;
+import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
+import org.keycloak.broker.saml.SAMLIdentityProvider;
+import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
+import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.social.facebook.FacebookIdentityProvider;
+import org.keycloak.social.facebook.FacebookIdentityProviderFactory;
+import org.keycloak.social.github.GitHubIdentityProvider;
+import org.keycloak.social.github.GitHubIdentityProviderFactory;
+import org.keycloak.social.google.GoogleIdentityProvider;
+import org.keycloak.social.google.GoogleIdentityProviderFactory;
+import org.keycloak.social.twitter.TwitterIdentityProvider;
+import org.keycloak.social.twitter.TwitterIdentityProviderFactory;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author pedroigor
+ */
+public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTest {
+
+ @Test
+ public void testInstallation() throws Exception {
+ RealmModel realm = installTestRealm();
+
+ assertIdentityProviderConfig(realm.getIdentityProviders());
+
+ assertTrue(realm.isIdentityFederationEnabled());
+
+ this.realmManager.removeRealm(realm);
+
+ commit();
+
+ realm = this.realmManager.getRealm(realm.getId());
+
+ assertNull(realm);
+ }
+
+ @Test
+ public void testUpdateIdentityProvider() throws Exception {
+ RealmModel realm = installTestRealm();
+ List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+
+ assertFalse(identityProviders.isEmpty());
+
+ IdentityProviderModel identityProviderModel = identityProviders.get(0);
+ String identityProviderId = identityProviderModel.getId();
+
+ identityProviderModel.setName("Changed Name");
+ identityProviderModel.getConfig().put("config-added", "value-added");
+ identityProviderModel.setEnabled(false);
+ identityProviderModel.setUpdateProfileFirstLogin(false);
+
+ realm.updateIdentityProvider(identityProviderModel);
+
+ commit();
+
+ realm = this.realmManager.getRealm(realm.getId());
+
+ identityProviderModel = realm.getIdentityProviderById(identityProviderId);
+
+ assertEquals("Changed Name", identityProviderModel.getName());
+ assertEquals("value-added", identityProviderModel.getConfig().get("config-added"));
+ assertEquals(false, identityProviderModel.isEnabled());
+ assertEquals(false, identityProviderModel.isUpdateProfileFirstLogin());
+
+ identityProviderModel.setName("Changed Name Again");
+ identityProviderModel.getConfig().remove("config-added");
+ identityProviderModel.setEnabled(true);
+ identityProviderModel.setUpdateProfileFirstLogin(true);
+
+ realm.updateIdentityProvider(identityProviderModel);
+
+ commit();
+
+ realm = this.realmManager.getRealm(realm.getId());
+ identityProviderModel = realm.getIdentityProviderById(identityProviderId);
+
+ assertEquals("Changed Name Again", identityProviderModel.getName());
+ assertFalse(identityProviderModel.getConfig().containsKey("config-added"));
+ assertEquals(true, identityProviderModel.isEnabled());
+ assertEquals(true, identityProviderModel.isUpdateProfileFirstLogin());
+ }
+
+ @Test
+ public void testRemoveIdentityProvider() throws Exception {
+ RealmModel realm = installTestRealm();
+ List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
+
+ assertFalse(identityProviders.isEmpty());
+
+ IdentityProviderModel identityProviderModel = identityProviders.get(0);
+ String expectedId = identityProviderModel.getId();
+
+ realm.removeIdentityProviderById(expectedId);
+
+ commit();
+
+ realm = this.realmManager.getRealm(realm.getId());
+
+ assertNull(realm.getIdentityProviderById(expectedId));
+ }
+
+ private void assertIdentityProviderConfig(List<IdentityProviderModel> identityProviders) {
+ assertFalse(identityProviders.isEmpty());
+
+ Set<String> checkedProviders = new HashSet<String>(getExpectedProviders());
+
+ for (IdentityProviderModel identityProvider : identityProviders) {
+ String providerId = identityProvider.getProviderId();
+
+ if (SAMLIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
+ assertSamlIdentityProviderConfig(identityProvider);
+ } else if (GoogleIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
+ assertGoogleIdentityProviderConfig(identityProvider);
+ } else if (OIDCIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
+ assertOidcIdentityProviderConfig(identityProvider);
+ } else if (FacebookIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
+ assertFacebookIdentityProviderConfig(identityProvider);
+ } else if (GitHubIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
+ assertGitHubIdentityProviderConfig(identityProvider);
+ } else if (TwitterIdentityProviderFactory.PROVIDER_ID.equals(providerId)) {
+ assertTwitterIdentityProviderConfig(identityProvider);
+ }
+
+ checkedProviders.remove(providerId);
+ }
+
+ assertTrue(checkedProviders.isEmpty());
+ }
+
+ private void assertGoogleIdentityProviderConfig(IdentityProviderModel identityProvider) {
+ GoogleIdentityProvider googleIdentityProvider = new GoogleIdentityProviderFactory().create(identityProvider);
+ OIDCIdentityProviderConfig config = googleIdentityProvider.getConfig();
+
+ assertEquals("google", config.getId());
+ assertEquals(GoogleIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+ assertEquals("Google", config.getName());
+ assertEquals(true, config.isEnabled());
+ assertEquals(true, config.isUpdateProfileFirstLogin());
+ assertEquals("clientId", config.getClientId());
+ assertEquals("clientSecret", config.getClientSecret());
+ assertEquals(GoogleIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
+ assertEquals(GoogleIdentityProvider.TOKEN_URL, config.getTokenUrl());
+ assertEquals(GoogleIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
+
+ }
+
+ private void assertSamlIdentityProviderConfig(IdentityProviderModel identityProvider) {
+ SAMLIdentityProvider samlIdentityProvider = new SAMLIdentityProviderFactory().create(identityProvider);
+ SAMLIdentityProviderConfig config = samlIdentityProvider.getConfig();
+
+ assertEquals("saml-idp", config.getId());
+ assertEquals(SAMLIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+ assertEquals("SAML IdP", config.getName());
+ assertEquals(true, config.isEnabled());
+ assertEquals(true, config.isUpdateProfileFirstLogin());
+ assertEquals("http://localhost:8080/idp/", config.getSingleSignOnServiceUrl());
+ assertEquals("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", config.getNameIDPolicyFormat());
+ assertEquals("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", config.getSigningPublicKey());
+ assertEquals(true, config.isWantAuthnRequestsSigned());
+ assertEquals(true, config.isForceAuthn());
+ assertEquals(true, config.isPostBindingAuthnRequest());
+ assertEquals(true, config.isPostBindingResponse());
+ assertEquals(true, config.isValidateSignature());
+ }
+
+ private void assertOidcIdentityProviderConfig(IdentityProviderModel identityProvider) {
+ OIDCIdentityProvider googleIdentityProvider = new OIDCIdentityProviderFactory().create(identityProvider);
+ OIDCIdentityProviderConfig config = googleIdentityProvider.getConfig();
+
+ assertEquals("oidc-idp", config.getId());
+ assertEquals(OIDCIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+ assertEquals("OIDC IdP", config.getName());
+ assertEquals(false, config.isEnabled());
+ assertEquals(false, config.isUpdateProfileFirstLogin());
+ assertEquals("clientId", config.getClientId());
+ assertEquals("clientSecret", config.getClientSecret());
+ }
+
+ private void assertFacebookIdentityProviderConfig(IdentityProviderModel identityProvider) {
+ FacebookIdentityProvider facebookIdentityProvider = new FacebookIdentityProviderFactory().create(identityProvider);
+ OAuth2IdentityProviderConfig config = facebookIdentityProvider.getConfig();
+
+ assertEquals("facebook", config.getId());
+ assertEquals(FacebookIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+ assertEquals("Facebook", config.getName());
+ assertEquals(true, config.isEnabled());
+ assertEquals(true, config.isUpdateProfileFirstLogin());
+ assertEquals("clientId", config.getClientId());
+ assertEquals("clientSecret", config.getClientSecret());
+ assertEquals(FacebookIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
+ assertEquals(FacebookIdentityProvider.TOKEN_URL, config.getTokenUrl());
+ assertEquals(FacebookIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
+ }
+
+ private void assertGitHubIdentityProviderConfig(IdentityProviderModel identityProvider) {
+ GitHubIdentityProvider gitHubIdentityProvider = new GitHubIdentityProviderFactory().create(identityProvider);
+ OAuth2IdentityProviderConfig config = gitHubIdentityProvider.getConfig();
+
+ assertEquals("github", config.getId());
+ assertEquals(GitHubIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+ assertEquals("GitHub", config.getName());
+ assertEquals(true, config.isEnabled());
+ assertEquals(true, config.isUpdateProfileFirstLogin());
+ assertEquals("clientId", config.getClientId());
+ assertEquals("clientSecret", config.getClientSecret());
+ assertEquals(GitHubIdentityProvider.AUTH_URL, config.getAuthorizationUrl());
+ assertEquals(GitHubIdentityProvider.TOKEN_URL, config.getTokenUrl());
+ assertEquals(GitHubIdentityProvider.PROFILE_URL, config.getUserInfoUrl());
+ }
+
+ private void assertTwitterIdentityProviderConfig(IdentityProviderModel identityProvider) {
+ TwitterIdentityProvider gitHubIdentityProvider = new TwitterIdentityProviderFactory().create(identityProvider);
+ OAuth2IdentityProviderConfig config = gitHubIdentityProvider.getConfig();
+
+ assertEquals("twitter", config.getId());
+ assertEquals(TwitterIdentityProviderFactory.PROVIDER_ID, config.getProviderId());
+ assertEquals("Twitter", config.getName());
+ assertEquals(true, config.isEnabled());
+ assertEquals(true, config.isUpdateProfileFirstLogin());
+ assertEquals("clientId", config.getClientId());
+ assertEquals("clientSecret", config.getClientSecret());
+ }
+
+ private RealmModel installTestRealm() throws IOException {
+ RealmRepresentation realmRepresentation = loadJson("model/test-realm-with-identity-provider.json");
+
+ assertNotNull(realmRepresentation);
+ assertEquals("test-realm-with-identity-provider", realmRepresentation.getRealm());
+
+ RealmModel realmModel = this.realmManager.importRealm(realmRepresentation);
+
+ commit();
+
+ realmModel = this.realmManager.getRealm(realmModel.getId());
+
+ assertNotNull(realmModel);
+
+ return realmModel;
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java
new file mode 100644
index 0000000..4ef9241
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProvider.java
@@ -0,0 +1,48 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.testsuite.broker.provider;
+
+import org.keycloak.broker.provider.AbstractIdentityProvider;
+import org.keycloak.broker.provider.AuthenticationRequest;
+import org.keycloak.broker.provider.AuthenticationResponse;
+import org.keycloak.models.IdentityProviderModel;
+
+/**
+ * @author pedroigor
+ */
+public class CustomIdentityProvider extends AbstractIdentityProvider<IdentityProviderModel> {
+
+ public CustomIdentityProvider(IdentityProviderModel config) {
+ super(config);
+ }
+
+ @Override
+ public AuthenticationResponse handleRequest(AuthenticationRequest request) {
+ return null;
+ }
+
+ @Override
+ public String getRelayState(AuthenticationRequest request) {
+ return null;
+ }
+
+ @Override
+ public AuthenticationResponse handleResponse(AuthenticationRequest request) {
+ return null;
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProviderFactory.java
new file mode 100644
index 0000000..b6ebb45
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/provider/CustomIdentityProviderFactory.java
@@ -0,0 +1,42 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.testsuite.broker.provider;
+
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+
+/**
+ * @author pedroigor
+ */
+public class CustomIdentityProviderFactory extends AbstractIdentityProviderFactory<CustomIdentityProvider> {
+
+ @Override
+ public String getName() {
+ return "Custom Identity Provider";
+ }
+
+ @Override
+ public CustomIdentityProvider create(IdentityProviderModel model) {
+ return new CustomIdentityProvider(model);
+ }
+
+ @Override
+ public String getId() {
+ return "custom-identity-provider";
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/social/CustomSocialProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/social/CustomSocialProvider.java
new file mode 100644
index 0000000..ae5d300
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/social/CustomSocialProvider.java
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.testsuite.broker.social;
+
+import org.keycloak.broker.provider.AbstractIdentityProvider;
+import org.keycloak.broker.provider.AuthenticationRequest;
+import org.keycloak.broker.provider.AuthenticationResponse;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.social.SocialIdentityProvider;
+
+/**
+ * @author pedroigor
+ */
+public class CustomSocialProvider extends AbstractIdentityProvider<IdentityProviderModel> implements SocialIdentityProvider<IdentityProviderModel> {
+
+ public CustomSocialProvider(IdentityProviderModel config) {
+ super(config);
+ }
+
+ @Override
+ public AuthenticationResponse handleRequest(AuthenticationRequest request) {
+ return null;
+ }
+
+ @Override
+ public String getRelayState(AuthenticationRequest request) {
+ return null;
+ }
+
+ @Override
+ public AuthenticationResponse handleResponse(AuthenticationRequest request) {
+ return null;
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/social/CustomSocialProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/social/CustomSocialProviderFactory.java
new file mode 100644
index 0000000..5fb2031
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/social/CustomSocialProviderFactory.java
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source
+ *
+ * Copyright 2013 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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.keycloak.testsuite.broker.social;
+
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.social.SocialIdentityProviderFactory;
+
+/**
+ * @author pedroigor
+ */
+public class CustomSocialProviderFactory extends AbstractIdentityProviderFactory<CustomSocialProvider> implements SocialIdentityProviderFactory<CustomSocialProvider> {
+
+ @Override
+ public String getName() {
+ return "Custom Social Provider";
+ }
+
+ @Override
+ public CustomSocialProvider create(IdentityProviderModel model) {
+ return new CustomSocialProvider(model);
+ }
+
+ @Override
+ public String getId() {
+ return "custom-social-provider";
+ }
+}
diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
new file mode 100644
index 0000000..3065616
--- /dev/null
+++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
@@ -0,0 +1 @@
+org.keycloak.testsuite.broker.provider.CustomIdentityProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.social.SocialIdentityProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.social.SocialIdentityProviderFactory
new file mode 100644
index 0000000..06a646c
--- /dev/null
+++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.social.SocialIdentityProviderFactory
@@ -0,0 +1 @@
+org.keycloak.testsuite.broker.social.CustomSocialProviderFactory
\ No newline at end of file
diff --git a/testsuite/integration/src/test/resources/model/test-realm-with-identity-provider.json b/testsuite/integration/src/test/resources/model/test-realm-with-identity-provider.json
new file mode 100755
index 0000000..03a63dd
--- /dev/null
+++ b/testsuite/integration/src/test/resources/model/test-realm-with-identity-provider.json
@@ -0,0 +1,183 @@
+{
+ "realm": "test-realm-with-identity-provider",
+ "enabled": true,
+ "requiredCredentials": [ "password" ],
+ "defaultRoles": [ "foo", "bar" ],
+ "identityProviders" : [
+ {
+ "id" : "google",
+ "providerId" : "google",
+ "name" : "Google",
+ "enabled": true,
+ "updateProfileFirstLogin" : "true",
+ "config": {
+ "clientId": "clientId",
+ "clientSecret": "clientSecret"
+ }
+ },
+ {
+ "id" : "facebook",
+ "providerId" : "facebook",
+ "name" : "Facebook",
+ "enabled": true,
+ "updateProfileFirstLogin" : "true",
+ "config": {
+ "authorizationUrl": "authorizationUrl",
+ "tokenUrl": "tokenUrl",
+ "userInfoUrl": "userInfoUrl",
+ "clientId": "clientId",
+ "clientSecret": "clientSecret"
+ }
+ },
+ {
+ "id" : "github",
+ "providerId" : "github",
+ "name" : "GitHub",
+ "enabled": true,
+ "updateProfileFirstLogin" : "true",
+ "config": {
+ "authorizationUrl": "authorizationUrl",
+ "tokenUrl": "tokenUrl",
+ "userInfoUrl": "userInfoUrl",
+ "clientId": "clientId",
+ "clientSecret": "clientSecret"
+ }
+ },
+ {
+ "id" : "twitter",
+ "providerId" : "twitter",
+ "name" : "Twitter",
+ "enabled": true,
+ "updateProfileFirstLogin" : "true",
+ "config": {
+ "authorizationUrl": "authorizationUrl",
+ "tokenUrl": "tokenUrl",
+ "userInfoUrl": "userInfoUrl",
+ "clientId": "clientId",
+ "clientSecret": "clientSecret"
+ }
+ },
+ {
+ "id" : "saml-idp",
+ "providerId" : "saml",
+ "name" : "SAML IdP",
+ "enabled": true,
+ "updateProfileFirstLogin" : "true",
+ "config": {
+ "singleSignOnServiceUrl": "http://localhost:8080/idp/",
+ "nameIDPolicyFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
+ "signingPublicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "wantAuthnRequestsSigned": true,
+ "forceAuthn": true,
+ "validateSignature": true,
+ "postBindingResponse": true,
+ "postBindingAuthnRequest": true
+ }
+ },
+ {
+ "id" : "oidc-idp",
+ "providerId" : "oidc",
+ "name" : "OIDC IdP",
+ "enabled": false,
+ "updateProfileFirstLogin" : "false",
+ "config": {
+ "clientId": "clientId",
+ "clientSecret": "clientSecret",
+ "prompt": "prompt",
+ "authorizationUrl": "authorizationUrl",
+ "tokenUrl": "tokenUrl",
+ "userInfoUrl": "userInfoUrl",
+ "defaultScope": "defaultScope",
+ "issuer": "issuer"
+ }
+ }
+ ],
+ "users": [
+ {
+ "username": "federated-user",
+ "enabled": true,
+ "federatedIdentities": [
+ {
+ "identityProvider": "facebook",
+ "userId": "facebook1",
+ "userName": "fbuser1"
+ },
+ {
+ "identityProvider": "twitter",
+ "userId": "twitter1",
+ "userName": "twuser1"
+ },
+ {
+ "identityProvider": "google",
+ "userId": "google1",
+ "userName": "mySocialUser@gmail.com"
+ }
+ ]
+ }
+ ],
+ "applications": [
+ {
+ "name": "Application",
+ "enabled": true,
+ "nodeReRegistrationTimeout": 50,
+ "registeredNodes": {
+ "node1": 10,
+ "172.10.15.20": 20
+ }
+ },
+ {
+ "name": "OtherApp",
+ "enabled": true
+ }
+
+ ],
+ "oauthClients" : [
+ {
+ "name" : "oauthclient",
+ "enabled": true,
+ "secret": "clientpassword"
+ }
+ ],
+ "roles" : {
+ "realm" : [
+ {
+ "name": "admin"
+ }
+ ],
+ "application" : {
+ "Application" : [
+ {
+ "name": "app-admin"
+ },
+ {
+ "name": "app-user"
+ }
+ ],
+ "OtherApp" : [
+ {
+ "name": "otherapp-admin"
+ },
+ {
+ "name": "otherapp-user"
+ }
+ ]
+ }
+ },
+ "scopeMappings": [
+ {
+ "client": "oauthclient",
+ "roles": ["admin"]
+ }
+ ],
+ "applicationScopeMappings": {
+ "Application": [
+ {
+ "client": "oauthclient",
+ "roles": ["app-user"]
+ }
+ ]
+
+ }
+
+
+}
\ No newline at end of file