Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Bill Burke 2015-11-12 11:31:55 -05:00
commit 0d20e3c7ff
35 changed files with 1141 additions and 253 deletions

View file

@ -1,18 +1,5 @@
package org.keycloak.connections.jpa; package org.keycloak.connections.jpa;
import org.hibernate.ejb.AvailableSettings;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DatabaseMetaData; import java.sql.DatabaseMetaData;
import java.sql.DriverManager; import java.sql.DriverManager;
@ -22,10 +9,25 @@ import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.sql.DataSource;
import org.hibernate.ejb.AvailableSettings;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ServerInfoAwareProviderFactory;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory { public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory, ServerInfoAwareProviderFactory {
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class); private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);

View file

@ -1,10 +1,10 @@
package org.keycloak.connections.jpa; package org.keycloak.connections.jpa;
import org.keycloak.provider.ServerInfoAwareProviderFactory; import org.keycloak.provider.ProviderFactory;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public interface JpaConnectionProviderFactory extends ServerInfoAwareProviderFactory<JpaConnectionProvider> { public interface JpaConnectionProviderFactory extends ProviderFactory<JpaConnectionProvider> {
} }

View file

@ -1,11 +1,12 @@
package org.keycloak.connections.mongo; package org.keycloak.connections.mongo;
import com.mongodb.DB; import java.lang.reflect.Method;
import com.mongodb.MongoClient; import java.net.UnknownHostException;
import com.mongodb.MongoClientOptions; import java.util.Collections;
import com.mongodb.MongoClientURI; import java.util.LinkedHashMap;
import com.mongodb.MongoCredential; import java.util.Map;
import com.mongodb.ServerAddress;
import javax.net.ssl.SSLSocketFactory;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
@ -15,18 +16,19 @@ import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocati
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider; import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ServerInfoAwareProviderFactory;
import javax.net.ssl.SSLSocketFactory; import com.mongodb.DB;
import java.lang.reflect.Method; import com.mongodb.MongoClient;
import java.net.UnknownHostException; import com.mongodb.MongoClientOptions;
import java.util.Collections; import com.mongodb.MongoClientURI;
import java.util.LinkedHashMap; import com.mongodb.MongoCredential;
import java.util.Map; import com.mongodb.ServerAddress;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory { public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory, ServerInfoAwareProviderFactory {
// TODO Make it dynamic // TODO Make it dynamic
private String[] entities = new String[]{ private String[] entities = new String[]{

View file

@ -1,9 +1,9 @@
package org.keycloak.connections.mongo; package org.keycloak.connections.mongo;
import org.keycloak.provider.ServerInfoAwareProviderFactory; import org.keycloak.provider.ProviderFactory;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
public interface MongoConnectionProviderFactory extends ServerInfoAwareProviderFactory<MongoConnectionProvider> { public interface MongoConnectionProviderFactory extends ProviderFactory<MongoConnectionProvider> {
} }

View file

@ -114,6 +114,10 @@
<name>picketlink.version</name> <name>picketlink.version</name>
<value>${picketlink.version}</value> <value>${picketlink.version}</value>
</injection> </injection>
<injection>
<name>wildfly.version</name>
<value>${wildfly.version}</value>
</injection>
</injections> </injections>
<options> <options>
<xmlTransformerType>saxon</xmlTransformerType> <xmlTransformerType>saxon</xmlTransformerType>

View file

@ -16,7 +16,7 @@
</para> </para>
<para> <para>
For example to implement the Event Listener SPI you start by implementing EventListenerProviderFactory: For example to implement the Event Listener SPI you start by implementing EventListenerProviderFactory:
<programlisting><![CDATA[{ <programlisting><![CDATA[
package org.acme.provider; package org.acme.provider;
import ... import ...
@ -43,7 +43,7 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
} }
} }
}]]></programlisting> ]]></programlisting>
The example uses an imagined MaxList which has a maximum size and is concurrency safe. When the maximum size is reached The example uses an imagined MaxList which has a maximum size and is concurrency safe. When the maximum size is reached
and new entries are added the oldest entry is removed. Keycloak creates a single instance of 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 EventListenerProviderFactory which makes it possible to store state for multiple requests. EventListenerProvider
@ -51,7 +51,7 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
</para> </para>
<para> <para>
Next you would implement EventListenerProvider: Next you would implement EventListenerProvider:
<programlisting><![CDATA[{ <programlisting><![CDATA[
package org.acme.provider; package org.acme.provider;
import ... import ...
@ -75,13 +75,61 @@ public class MyEventListenerProvider implements EventListenerProvider {
} }
} }
}]]></programlisting> ]]></programlisting>
</para> </para>
<para> <para>
The file <literal>META-INF/services/org.keycloak.events.EventListenerProviderFactory</literal> should The file <literal>META-INF/services/org.keycloak.events.EventListenerProviderFactory</literal> should
contain the full name of your ProviderFactory implementation: contain the full name of your ProviderFactory implementation:
<programlisting><![CDATA[org.acme.provider.MyEventListenerProviderFactory]]></programlisting> <programlisting><![CDATA[org.acme.provider.MyEventListenerProviderFactory]]></programlisting>
</para> </para>
<section>
<title>Show info from you SPI implementation in Keycloak admin console</title>
<para>
Sometimes it is useful to show additional info about your Provider to a Keycloak administrator.
You can show provider build time informations (eg. version of custom provider currently installed),
current configuration of the provider (eg. url of remote system your provider talks to) or some operational
info (average time of response from remote system your provider talks to).
Keycloak admin console provides Server Info page to show this kind of information.
</para>
<para>
To show info from your provider it is enough to implement
<literal>org.keycloak.provider.ServerInfoAwareProviderFactory</literal> interface in your ProviderFactory.
Example implementation for MyEventListenerProviderFactory from previous example:
<programlisting><![CDATA[
package org.acme.provider;
import ...
public class MyEventListenerProviderFactory implements EventListenerProviderFactory, ServerInfoAwareProviderFactory {
private List<Event> events;
private int max;
...
@Override
public void init(Config.Scope config) {
max = config.getInt("max");
events = new MaxList(max);
}
...
@Override
public Map<String, String> getOperationalInfo() {
Map<String, String> ret = new LinkedHashMap<>();
ret.put("version", "1.0");
ret.put("listSizeMax", max + "");
ret.put("listSizeCurrent", events.size() + "");
return ret;
}
}
]]></programlisting>
</para>
</section>
</section> </section>
<section> <section>
@ -98,13 +146,13 @@ public class MyEventListenerProvider implements EventListenerProvider {
To register a provider using Modules first create a module. To do this you can either use the jboss-cli To register a provider using Modules first create a module. To do this you can either use the jboss-cli
script or manually create a folder inside KEYCLOAK_HOME/modules and add your jar and a <literal>module.xml</literal>. script or manually create a folder inside KEYCLOAK_HOME/modules and add your jar and a <literal>module.xml</literal>.
For example to add the event listener sysout example provider using the jboss-cli script execute: For example to add the event listener sysout example provider using the jboss-cli script execute:
<programlisting><![CDATA[{ <programlisting><![CDATA[
KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-model-api,org.keycloak.keycloak-events-api" KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.event-sysout --resources=target/event-listener-sysout-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-model-api,org.keycloak.keycloak-events-api"
}]]></programlisting> ]]></programlisting>
Or to manually create it start by creating the folder <literal>KEYCLOAK_HOME/modules/org/keycloak/examples/event-sysout/main</literal>. Or to manually create it start by creating the folder <literal>KEYCLOAK_HOME/modules/org/keycloak/examples/event-sysout/main</literal>.
Then copy <literal>event-listener-sysout-example.jar</literal> to this folder and create <literal>module.xml</literal> Then copy <literal>event-listener-sysout-example.jar</literal> to this folder and create <literal>module.xml</literal>
with the following content: with the following content:
<programlisting><![CDATA[{ <programlisting><![CDATA[
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.examples.event-sysout"> <module xmlns="urn:jboss:module:1.1" name="org.keycloak.examples.event-sysout">
<resources> <resources>
@ -116,7 +164,7 @@ public class MyEventListenerProvider implements EventListenerProvider {
<module name="org.keycloak.keycloak-events-api"/> <module name="org.keycloak.keycloak-events-api"/>
</dependencies> </dependencies>
</module> </module>
}]]></programlisting> ]]></programlisting>
</para> </para>
<para> <para>
Once you've created the module you need to register this module with Keycloak. This is done by editing Once you've created the module you need to register this module with Keycloak. This is done by editing

View file

@ -43,9 +43,9 @@
<section id="overlay_install"> <section id="overlay_install">
<title>Install on existing WildFly 9.0.1.Final</title> <title>Install on existing WildFly &wildfly.version;</title>
<para> <para>
Keycloak can be installed into an existing WildFly 9.0.0.Final server. To do this download Keycloak can be installed into an existing WildFly &wildfly.version; server. To do this download
<literal>keycloak-overlay-&project.version;.zip</literal> or <literal>keycloak-overlay-&project.version;.tar.gz</literal>. <literal>keycloak-overlay-&project.version;.zip</literal> or <literal>keycloak-overlay-&project.version;.tar.gz</literal>.
Once downloaded extract into the root directory of your WildFly installation. To start WildFly with Keycloak Once downloaded extract into the root directory of your WildFly installation. To start WildFly with Keycloak
run: run:
@ -62,11 +62,15 @@
<para> <para>
To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with
the desired server-config. If you are running the server in standalone mode run: the desired server-config. If you are running the server in standalone mode run:
<programlisting>cd &lt;WILDFLY_HOME&gt;/bin <programlisting>
./jboss-cli.sh -c --file=keycloak-install.cli</programlisting> cd &lt;WILDFLY_HOME&gt;/bin
./jboss-cli.sh -c --file=keycloak-install.cli
</programlisting>
Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run: Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run:
<programlisting>cd &lt;WILDFLY_HOME&gt;/bin <programlisting>
./jboss-cli.sh -c --file=keycloak-install-ha.cli</programlisting> cd &lt;WILDFLY_HOME&gt;/bin
./jboss-cli.sh -c --file=keycloak-install-ha.cli
</programlisting>
You may see exceptions in the server log, but after restarting the server they should be gone. You may see exceptions in the server log, but after restarting the server they should be gone.
You can restart the server with: You can restart the server with:
<programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c :reload</programlisting> <programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c :reload</programlisting>
@ -75,7 +79,7 @@
<section> <section>
<title>Install on existing JBoss EAP 6.4.0.GA</title> <title>Install on existing JBoss EAP 6.4.0.GA</title>
<para> <para>
Same procedure as WildFly 9.0.1.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>. Same procedure as WildFly &wildfly.version;, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
</para> </para>
</section> </section>
<section> <section>
@ -85,7 +89,7 @@
To install it first download <literal>keycloak-demo-&project.version;.zip</literal> or To install it first download <literal>keycloak-demo-&project.version;.zip</literal> or
<literal>keycloak-demo-&project.version;.tar.gz</literal>. Once downloaded extract it inside <literal>keycloak-demo-&project.version;.tar.gz</literal>. Once downloaded extract it inside
<literal>keycloak-demo-&project.version;</literal> you'll find <literal>keycloak</literal> which contains <literal>keycloak-demo-&project.version;</literal> you'll find <literal>keycloak</literal> which contains
a full WildFly 9.0.0.Final server with Keycloak Server and Adapters included. You'll also find <literal>docs</literal> a full WildFly &wildfly.version; server with Keycloak Server and Adapters included. You'll also find <literal>docs</literal>
and <literal>examples</literal> which contains everything you need to get started developing applications that use Keycloak. and <literal>examples</literal> which contains everything you need to get started developing applications that use Keycloak.
</para> </para>
<para> <para>
@ -437,12 +441,12 @@ All configuration options are optional. Default value for directory is <literal>
settings you can specify before boot time. This is configured in the settings you can specify before boot time. This is configured in the
<literal>standalone/configuration/keycloak-server.json</literal>. <literal>standalone/configuration/keycloak-server.json</literal>.
By default the setting is like this: By default the setting is like this:
<programlisting><![CDATA[ <programlisting><![CDATA[
"connectionsHttpClient": { "connectionsHttpClient": {
"default": { "default": {
"disable-trust-manager": true "disable-trust-manager": true
} }
}, },
]]></programlisting> ]]></programlisting>
Possible configuration options are: Possible configuration options are:
<variablelist> <variablelist>
@ -659,8 +663,8 @@ All configuration options are optional. Default value for directory is <literal>
to do with the <literal>keytool</literal> utility that comes with the Java jdk. to do with the <literal>keytool</literal> utility that comes with the Java jdk.
</para> </para>
<para> <para>
<programlisting> <programlisting>
$ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950 $ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950
Enter keystore password: secret Enter keystore password: secret
Re-enter new password: secret Re-enter new password: secret
What is your first and last name? What is your first and last name?
@ -677,7 +681,7 @@ All configuration options are optional. Default value for directory is <literal>
[Unknown]: US [Unknown]: US
Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct? Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct?
[no]: yes [no]: yes
</programlisting> </programlisting>
</para> </para>
<para> <para>
You should answer <literal>What is your first and last name ?</literal> question with You should answer <literal>What is your first and last name ?</literal> question with
@ -693,44 +697,44 @@ All configuration options are optional. Default value for directory is <literal>
</para> </para>
<para> <para>
The first thing to do is generate a Certificate Request: The first thing to do is generate a Certificate Request:
<programlisting> <programlisting>
$ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq $ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq
</programlisting> </programlisting>
</para> </para>
<para> <para>
Where <literal>yourdomain</literal> is a DNS name for which this certificate is generated for. Where <literal>yourdomain</literal> is a DNS name for which this certificate is generated for.
Keytool generates the request: Keytool generates the request:
<programlisting> <programlisting>
-----BEGIN NEW CERTIFICATE REQUEST----- -----BEGIN NEW CERTIFICATE REQUEST-----
MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y
ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0 ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY
Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1 Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1
29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as 29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as
H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw
Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35 Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35
2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i 2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i
n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf
PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ
9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X 9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X
MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S
vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8= vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8=
-----END NEW CERTIFICATE REQUEST----- -----END NEW CERTIFICATE REQUEST-----
</programlisting> </programlisting>
</para> </para>
<para> <para>
Send this ca request to your CA. The CA will issue you a signed certificate and send it to you. Send this ca request to your CA. The CA will issue you a signed certificate and send it to you.
Before you import your new cert, you must obtain and import the root certificate of the CA. Before you import your new cert, you must obtain and import the root certificate of the CA.
You can download the cert from CA (ie.: root.crt) and import as follows: You can download the cert from CA (ie.: root.crt) and import as follows:
<programlisting> <programlisting>
$ keytool -import -keystore keycloak.jks -file root.crt -alias root $ keytool -import -keystore keycloak.jks -file root.crt -alias root
</programlisting> </programlisting>
</para> </para>
<para> <para>
Last step is import your new CA generated certificate to your keystore: Last step is import your new CA generated certificate to your keystore:
<programlisting> <programlisting>
$ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer
</programlisting> </programlisting>
</para> </para>
</section> </section>
</section> </section>
@ -744,18 +748,19 @@ All configuration options are optional. Default value for directory is <literal>
</para> </para>
<para> <para>
To the <literal>security-realms</literal> element add: To the <literal>security-realms</literal> element add:
<programlisting><![CDATA[<security-realm name="UndertowRealm"> <programlisting><![CDATA[
<security-realm name="UndertowRealm">
<server-identities> <server-identities>
<ssl> <ssl>
<keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="secret" /> <keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="secret" />
</ssl> </ssl>
</server-identities> </server-identities>
</security-realm>]]></programlisting> </security-realm>
]]></programlisting>
</para> </para>
<para> <para>
Find the element <literal>&lt;server name="default-server"&gt;</literal> (it's a child element of <literal>&lt;subsystem xmlns="urn:jboss:domain:undertow:1.0"&gt;</literal>) and add: Find the element <literal>&lt;server name="default-server"&gt;</literal> (it's a child element of <literal>&lt;subsystem xmlns="urn:jboss:domain:undertow:1.0"&gt;</literal>) and add:
<programlisting><![CDATA[<https-listener name="https" socket-binding="https" security-realm="UndertowRealm"/> <programlisting><![CDATA[<https-listener name="https" socket-binding="https" security-realm="UndertowRealm"/>]]></programlisting>
]]></programlisting>
</para> </para>
<para> <para>
Check the <ulink url="https://docs.jboss.org/author/display/WFLY8/Undertow+(web)+subsystem+configuration">Wildfly Undertow</ulink> documentation for more information on fine tuning the socket connections. Check the <ulink url="https://docs.jboss.org/author/display/WFLY8/Undertow+(web)+subsystem+configuration">Wildfly Undertow</ulink> documentation for more information on fine tuning the socket connections.
@ -865,7 +870,7 @@ All configuration options are optional. Default value for directory is <literal>
</para> </para>
<para> <para>
To do this, add the <literal>default-web-module</literal> attribute in the Undertow subystem in standalone.xml. To do this, add the <literal>default-web-module</literal> attribute in the Undertow subystem in standalone.xml.
<programlisting><![CDATA[ <programlisting><![CDATA[
<subsystem xmlns="urn:jboss:domain:undertow:2.0"> <subsystem xmlns="urn:jboss:domain:undertow:2.0">
<server name="default-server"> <server name="default-server">
<host name="default-host" alias="localhost" default-web-module="keycloak-server.war"> <host name="default-host" alias="localhost" default-web-module="keycloak-server.war">

View file

@ -877,7 +877,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
$scope.viewImportDetails = function() { $scope.viewImportDetails = function() {
$modal.open({ $modal.open({
templateUrl: resourceUrl + '/partials/modal/view-object.html', templateUrl: resourceUrl + '/partials/modal/view-object.html',
controller: 'JsonModalCtrl', controller: 'ObjectModalCtrl',
resolve: { resolve: {
object: function () { object: function () {
return $scope.client; return $scope.client;

View file

@ -37,7 +37,7 @@
<td>{{mapper.name}}</td> <td>{{mapper.name}}</td>
<td>{{mapperTypes[mapper.protocolMapper].category}}</td> <td>{{mapperTypes[mapper.protocolMapper].category}}</td>
<td>{{mapperTypes[mapper.protocolMapper].name}}</td> <td>{{mapperTypes[mapper.protocolMapper].name}}</td>
<td><input type="checkbox" ng-model="mapper.isChecked"></td> <td><input type="checkbox" ng-model="mapper.isChecked" id="{{mapper.protocolMapper}}"></td>
</tr> </tr>
<tr data-ng-show="mappers.length == 0"> <tr data-ng-show="mappers.length == 0">
<td>{{:: 'no-mappers-available' | translate}}</td> <td>{{:: 'no-mappers-available' | translate}}</td>

View file

@ -54,7 +54,7 @@
<div class="form-group" data-ng-show="realm.otpPolicyType == 'hotp'"> <div class="form-group" data-ng-show="realm.otpPolicyType == 'hotp'">
<label class="col-md-2 control-label" for="counter">Initial Counter</label> <label class="col-md-2 control-label" for="counter">Initial Counter</label>
<div class="col-md-6"> <div class="col-md-6">
<input class="form-control" type="text" id="counter" name="counter" data-ng-model="realm.otpPolicyInitialCounter" autofocus> <input class="form-control" type="number" required min="1" max="120" id="counter" name="counter" data-ng-model="realm.otpPolicyInitialCounter" autofocus>
</div> </div>
<kc-tooltip>What should the initial counter value be?</kc-tooltip> <kc-tooltip>What should the initial counter value be?</kc-tooltip>
</div> </div>

View file

@ -20,8 +20,8 @@
<tbody> <tbody>
<tr ng-repeat="requiredAction in requiredActions" data-ng-show="requiredActions.length > 0"> <tr ng-repeat="requiredAction in requiredActions" data-ng-show="requiredActions.length > 0">
<td>{{requiredAction.name}}</td> <td>{{requiredAction.name}}</td>
<td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)"></td> <td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.enabled"></td>
<td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)"></td> <td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.defaultAction"></td>
</tr> </tr>
<tr data-ng-show="requiredActions.length == 0"> <tr data-ng-show="requiredActions.length == 0">
<td>No required actions configured</td> <td>No required actions configured</td>

View file

@ -43,21 +43,21 @@
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId> <artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version> <version>${jetty9.version}</version>
<scope>compile</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId> <artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version> <version>${jetty9.version}</version>
<scope>compile</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId> <artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version> <version>${jetty9.version}</version>
<scope>compile</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>

View file

@ -153,23 +153,23 @@
+ '&response_type=code'; + '&response_type=code';
if (options && options.prompt) { if (options && options.prompt) {
url += '&prompt=' + options.prompt; url += '&prompt=' + encodeURIComponent(options.prompt);
} }
if (options && options.loginHint) { if (options && options.loginHint) {
url += '&login_hint=' + options.loginHint; url += '&login_hint=' + encodeURIComponent(options.loginHint);
} }
if (options && options.idpHint) { if (options && options.idpHint) {
url += '&kc_idp_hint=' + options.idpHint; url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
} }
if (options && options.scope) { if (options && options.scope) {
url += '&scope=' + options.scope; url += '&scope=' + encodeURIComponent(options.scope);
} }
if (options && options.locale) { if (options && options.locale) {
url += '&ui_locales=' + options.locale; url += '&ui_locales=' + encodeURIComponent(options.locale);
} }
return url; return url;

View file

@ -1,9 +1,9 @@
package org.keycloak.adapters.osgi; package org.keycloak.adapters.osgi;
import java.net.URL; import java.net.URL;
import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Random;
import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Constraint;
@ -133,7 +133,8 @@ public class PaxWebIntegrationService {
Constraint constraint = constraintMapping.getConstraint(); Constraint constraint = constraintMapping.getConstraint();
String[] roles = constraint.getRoles(); String[] roles = constraint.getRoles();
// name property is unavailable on constraint object :/ // name property is unavailable on constraint object :/
String name = "Constraint-" + new Random().nextInt();
String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
int dataConstraint = constraint.getDataConstraint(); int dataConstraint = constraint.getDataConstraint();
String dataConstraintStr; String dataConstraintStr;

View file

@ -3,15 +3,15 @@ package org.keycloak.provider;
import java.util.Map; import java.util.Map;
/** /**
* Marker interface for ProviderFactory of Provider which wants to show some info on "Server Info" page in Admin console. * Marker interface for {@link ProviderFactory} of Provider which wants to show some info on "Server Info" page in Admin console.
* *
* @author Vlastimil Elias (velias at redhat dot com) * @author Vlastimil Elias (velias at redhat dot com)
*/ */
public interface ServerInfoAwareProviderFactory<T extends Provider> extends ProviderFactory<T> { public interface ServerInfoAwareProviderFactory {
/** /**
* Get operational info about given provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is * Return actual info about the provider. This info contains informations about providers configuration and operational conditions (eg. errors in connection to remote systems etc) which is
* shown on "Server Info" page. * shown on "Server Info" page then.
* *
* @return Map with keys describing value and relevant values itself * @return Map with keys describing value and relevant values itself
*/ */

View file

@ -48,8 +48,8 @@
<dom4j.version>1.6.1</dom4j.version> <dom4j.version>1.6.1</dom4j.version>
<xml-apis.version>1.4.01</xml-apis.version> <xml-apis.version>1.4.01</xml-apis.version>
<slf4j.version>1.7.7</slf4j.version> <slf4j.version>1.7.7</slf4j.version>
<wildfly.version>9.0.1.Final</wildfly.version> <wildfly.version>9.0.2.Final</wildfly.version>
<wildfly.core.version>1.0.1.Final</wildfly.core.version> <wildfly.core.version>1.0.2.Final</wildfly.core.version>
<wildfly.build-tools.version>1.0.0.Final</wildfly.build-tools.version> <wildfly.build-tools.version>1.0.0.Final</wildfly.build-tools.version>
<!-- this is EAP 6.4 alpha, publicly available --> <!-- this is EAP 6.4 alpha, publicly available -->

View file

@ -1,5 +1,17 @@
package org.keycloak.services.resources.admin.info; package org.keycloak.services.resources.admin.info;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import javax.ws.rs.GET;
import javax.ws.rs.core.Context;
import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.broker.provider.IdentityProviderFactory; import org.keycloak.broker.provider.IdentityProviderFactory;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
@ -21,10 +33,6 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation; import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
import org.keycloak.social.SocialIdentityProvider; import org.keycloak.social.SocialIdentityProvider;
import javax.ws.rs.GET;
import javax.ws.rs.core.Context;
import java.util.*;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/ */
@ -73,7 +81,6 @@ public class ServerInfoAdminResource {
for (Spi spi : spis) { for (Spi spi : spis) {
SpiInfoRepresentation spiRep = new SpiInfoRepresentation(); SpiInfoRepresentation spiRep = new SpiInfoRepresentation();
spiRep.setInternal(spi.isInternal()); spiRep.setInternal(spi.isInternal());
spiRep.setSystemInfo(ServerInfoAwareProviderFactory.class.isAssignableFrom(spi.getProviderFactoryClass()));
List<String> providerIds = new LinkedList<>(session.listProviderIds(spi.getProviderClass())); List<String> providerIds = new LinkedList<>(session.listProviderIds(spi.getProviderClass()));
Collections.sort(providerIds); Collections.sort(providerIds);
@ -83,8 +90,9 @@ public class ServerInfoAdminResource {
if (providerIds != null) { if (providerIds != null) {
for (String name : providerIds) { for (String name : providerIds) {
ProviderRepresentation provider = new ProviderRepresentation(); ProviderRepresentation provider = new ProviderRepresentation();
if (spiRep.isSystemInfo()) { ProviderFactory<?> pi = session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name);
provider.setOperationalInfo(((ServerInfoAwareProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name)).getOperationalInfo()); if (ServerInfoAwareProviderFactory.class.isAssignableFrom(pi.getClass())) {
provider.setOperationalInfo(((ServerInfoAwareProviderFactory) pi).getOperationalInfo());
} }
providers.put(name, provider); providers.put(name, provider);
} }

View file

@ -8,7 +8,6 @@ import java.util.Map;
public class SpiInfoRepresentation { public class SpiInfoRepresentation {
private boolean internal; private boolean internal;
private boolean systemInfo;
private Map<String, ProviderRepresentation> providers; private Map<String, ProviderRepresentation> providers;
@ -20,14 +19,6 @@ public class SpiInfoRepresentation {
this.internal = internal; this.internal = internal;
} }
public boolean isSystemInfo() {
return systemInfo;
}
public void setSystemInfo(boolean systemInfo) {
this.systemInfo = systemInfo;
}
public Map<String, ProviderRepresentation> getProviders() { public Map<String, ProviderRepresentation> getProviders() {
return providers; return providers;
} }

View file

@ -15,7 +15,18 @@
<exclude.console>-</exclude.console> <exclude.console>-</exclude.console>
<exclude.account>-</exclude.account> <exclude.account>-</exclude.account>
</properties> </properties>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-util-embedded-ldap</artifactId>
<exclusions>
<exclusion>
<groupId>bouncycastle</groupId>
<artifactId>bcprov-jdk15</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>

View file

@ -57,7 +57,7 @@ public class AdminConsoleRealm extends AdminConsoleRealmsRoot {
private WebElement rolesLink; private WebElement rolesLink;
@FindBy(partialLinkText = "Identity Providers") @FindBy(partialLinkText = "Identity Providers")
private WebElement identityProvidersLink; private WebElement identityProvidersLink;
@FindBy(partialLinkText = "User Feferation") @FindBy(partialLinkText = "User Federation")
private WebElement userFederationLink; private WebElement userFederationLink;
@FindBy(partialLinkText = "Authentication") @FindBy(partialLinkText = "Authentication")
private WebElement authenticationLink; private WebElement authenticationLink;

View file

@ -3,7 +3,7 @@ package org.keycloak.testsuite.console.page.authentication;
import org.keycloak.testsuite.console.page.AdminConsoleRealm; import org.keycloak.testsuite.console.page.AdminConsoleRealm;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import static org.keycloak.testsuite.util.WaitUtils.waitAjaxForElement; import static org.keycloak.testsuite.util.WaitUtils.*;
/** /**
* @author tkyjovsk * @author tkyjovsk
@ -20,6 +20,9 @@ public class Authentication extends AdminConsoleRealm {
@FindBy(xpath = "//div[contains(@class, 'alert-success')]") @FindBy(xpath = "//div[contains(@class, 'alert-success')]")
private WebElement success; private WebElement success;
@FindBy(xpath = "//button[@class='close']/span")
private WebElement close;
public String getSuccessMessage() { public String getSuccessMessage() {
waitAjaxForElement(success); waitAjaxForElement(success);
return success.getText(); return success.getText();
@ -30,6 +33,13 @@ public class Authentication extends AdminConsoleRealm {
return error.getText(); return error.getText();
} }
public void closeNotification() {
if (close.isDisplayed()) {
close.click();
}
waitAjaxForElementNotVisible(close);
}
public AuthenticationTabs tabs() { public AuthenticationTabs tabs() {
return authenticationTabs; return authenticationTabs;
} }

View file

@ -7,11 +7,17 @@ import org.openqa.selenium.support.FindBy;
/** /**
* @author tkyjovsk * @author tkyjovsk
* @author mhajas * @author mhajas
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/ */
public class RequiredActions extends Authentication { public class RequiredActions extends Authentication {
public final static String ENABLED = "enabled"; public final static String ENABLED = ".enabled";
public final static String DEFAULT_ACTION = "defaultAction"; public final static String DEFAULT = ".defaultAction";
public final static String CONFIGURE_TOTP = "CONFIGURE_TOTP";
public final static String UPDATE_PROFILE = "UPDATE_PROFILE";
public final static String TERMS_AND_CONDITIONS = "terms_and_conditions";
public final static String UPDATE_PASSWORD = "UPDATE_PASSWORD";
public final static String VERIFY_EMAIL = "VERIFY_EMAIL";
@FindBy(tagName = "table") @FindBy(tagName = "table")
private WebElement requiredActionTable; private WebElement requiredActionTable;
@ -21,51 +27,59 @@ public class RequiredActions extends Authentication {
return super.getUriFragment() + "/required-actions"; return super.getUriFragment() + "/required-actions";
} }
private void setRequiredActionValue(String row, String column, boolean value) { private void setRequiredActionValue(String id, boolean value) {
WebElement checkbox = requiredActionTable.findElement(By.xpath("//td[text()='" + row + "']/..//input[@ng-model='requiredAction." + column + "']")); WebElement checkbox = requiredActionTable.findElement(By.id(id));
if (checkbox.isSelected() != value) { if (checkbox.isSelected() != value) {
checkbox.click(); checkbox.click();
} }
} }
private void setRequiredActionEnabledValue(String id, boolean value) {
setRequiredActionValue(id + ENABLED, value);
}
private void setRequiredActionDefaultValue(String id, boolean value) {
setRequiredActionValue(id + DEFAULT, value);
}
public void setTermsAndConditionEnabled(boolean value) { public void setTermsAndConditionEnabled(boolean value) {
setRequiredActionValue("Terms and Conditions", ENABLED, value); setRequiredActionEnabledValue(TERMS_AND_CONDITIONS, value);
} }
public void setTermsAndConditionDefaultAction(boolean value) { public void setTermsAndConditionDefaultAction(boolean value) {
setRequiredActionValue("Terms and Conditions", DEFAULT_ACTION, value); setRequiredActionDefaultValue(TERMS_AND_CONDITIONS, value);
} }
public void setVerifyEmailEnabled(boolean value) { public void setVerifyEmailEnabled(boolean value) {
setRequiredActionValue("Verify Email", ENABLED, value); setRequiredActionEnabledValue(VERIFY_EMAIL, value);
} }
public void setVerifyEmailDefaultAction(boolean value) { public void setVerifyEmailDefaultAction(boolean value) {
setRequiredActionValue("Verify Email", DEFAULT_ACTION, value); setRequiredActionDefaultValue(VERIFY_EMAIL, value);
} }
public void setUpdatePasswordEnabled(boolean value) { public void setUpdatePasswordEnabled(boolean value) {
setRequiredActionValue("Update Password", ENABLED, value); setRequiredActionEnabledValue(UPDATE_PASSWORD, value);
} }
public void setUpdatePasswordDefaultAction(boolean value) { public void setUpdatePasswordDefaultAction(boolean value) {
setRequiredActionValue("Update Password", DEFAULT_ACTION, value); setRequiredActionDefaultValue(UPDATE_PASSWORD, value);
} }
public void setConfigureTotpEnabled(boolean value) { public void setConfigureTotpEnabled(boolean value) {
setRequiredActionValue("Configure Totp", ENABLED, value); setRequiredActionEnabledValue(CONFIGURE_TOTP, value);
} }
public void setConfigureTotpDefaultAction(boolean value) { public void setConfigureTotpDefaultAction(boolean value) {
setRequiredActionValue("Configure Totp", DEFAULT_ACTION, value); setRequiredActionDefaultValue(CONFIGURE_TOTP, value);
} }
public void setUpdateProfileEnabled(boolean value) { public void setUpdateProfileEnabled(boolean value) {
setRequiredActionValue("Update Profile", ENABLED, value); setRequiredActionEnabledValue(UPDATE_PROFILE, value);
} }
public void setUpdateProfileDefaultAction(boolean value) { public void setUpdateProfileDefaultAction(boolean value) {
setRequiredActionValue("Update Profile", DEFAULT_ACTION, value); setRequiredActionDefaultValue(UPDATE_PROFILE, value);
} }
} }

View file

@ -1,14 +1,112 @@
package org.keycloak.testsuite.console.page.clients; package org.keycloak.testsuite.console.page.clients;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.testsuite.console.page.fragment.DataTable;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import java.util.ArrayList;
import java.util.List;
/** /**
* *
* @author tkyjovsk * @author tkyjovsk
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/ */
public class ClientMappers extends Client { public class ClientMappers extends Client {
public static final String ADD_BUILTIN = "Add Builtin";
@FindBy(tagName = "table")
private ClientMapperTable table;
@Override @Override
public String getUriFragment() { public String getUriFragment() {
return super.getUriFragment() + "/mappers"; return super.getUriFragment() + "/mappers";
} }
public ClientMapperTable mapperTable() {
return table;
}
public class ClientMapperTable extends DataTable {
public List<ProtocolMapperRepresentation> searchMappings(String searchPattern) {
search(searchPattern);
return getMappingsFromRows();
}
public void createMapper() {
waitAjaxForBody();
clickHeaderLink(CREATE);
}
public void addBuiltin() {
waitAjaxForBody();
clickHeaderLink(ADD_BUILTIN);
}
public void clickMapper(String mapperName) {
waitAjaxForBody();
body().findElement(By.linkText(mapperName)).click();
}
public void clickMapper(ProtocolMapperRepresentation mapper) {
clickMapper(mapper.getName());
}
private void clickMapperActionButton(String mapperName, String buttonText) {
waitAjaxForBody();
clickRowActionButton(getRowByLinkText(mapperName), buttonText);
}
private void clickMapperActionButton(ProtocolMapperRepresentation mapper, String buttonName) {
clickMapperActionButton(mapper.getName(), buttonName);
}
public void editMapper(String mapperName) {
clickMapperActionButton(mapperName, EDIT);
}
public void editMapper(ProtocolMapperRepresentation mapper) {
clickMapperActionButton(mapper, EDIT);
}
public void deleteMapper(String mapperName) {
clickMapperActionButton(mapperName, DELETE);
}
public void deleteMapper(ProtocolMapperRepresentation mapper) {
clickMapperActionButton(mapper, DELETE);
}
public ProtocolMapperRepresentation getMappingFromRow(WebElement row) {
if (!row.isDisplayed()) {return null;} // Is that necessary?
ProtocolMapperRepresentation mappingsRepresentation = new ProtocolMapperRepresentation();
List<WebElement> cols = row.findElements(By.tagName("td"));
mappingsRepresentation.setName(cols.get(0).getText());
//mappingsRepresentation.setProtocol(cols.get(1).getText());
mappingsRepresentation.setProtocolMapper(cols.get(2).getText());
return mappingsRepresentation;
}
public List<ProtocolMapperRepresentation> getMappingsFromRows() {
List<ProtocolMapperRepresentation> mappings = new ArrayList<ProtocolMapperRepresentation>();
for (WebElement row : rows()) {
ProtocolMapperRepresentation mapperRepresentation = getMappingFromRow(row);
if (mapperRepresentation != null) {
mappings.add(mapperRepresentation);
}
}
return mappings;
}
}
} }

View file

@ -0,0 +1,21 @@
package org.keycloak.testsuite.console.page.clients;
import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.testsuite.console.page.AdminConsoleCreate;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
public class CreateClientMappers extends AdminConsoleCreate {
@Page
private CreateClientMappersForm form;
public CreateClientMappers() {
setEntity("mappers");
}
public CreateClientMappersForm form() {
return form;
}
}

View file

@ -0,0 +1,187 @@
package org.keycloak.testsuite.console.page.clients;
import org.jboss.arquillian.test.api.ArquillianResource;
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
import org.keycloak.testsuite.page.Form;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select;
import java.util.List;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*
* TODO: SAML
*/
public class CreateClientMappersForm extends Form {
// Mappers types
public static final String HARDCODED_ROLE = "Hardcoded Role";
public static final String HARDCODED_CLAIM = "Hardcoded claim";
public static final String USER_SESSION_NOTE = "User Session Note";
public static final String ROLE_NAME_MAPPER = "Role Name Mapper";
public static final String USER_ADDRESS = "User Address";
public static final String USERS_FULL_NAME = "User's full name";
public static final String USER_ATTRIBUTE = "User Attribute";
public static final String USER_PROPERTY = "User Property";
@FindBy(id = "name")
private WebElement nameElement;
@FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='consentRequired']]")
private OnOffSwitch consentRequiredSwitch;
@FindBy(id = "consentText")
private WebElement consentTextElement;
@FindBy(id = "mapperTypeCreate")
private Select mapperTypeSelect;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Property']//following-sibling::node()//input[@type='text']")
private WebElement propertyInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='User Attribute']//following-sibling::node()//input[@type='text']")
private WebElement userAttributeInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='User Session Note']//following-sibling::node()//input[@type='text']")
private WebElement userSessionNoteInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Multivalued']//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch multivaluedInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Role']//following-sibling::node()//input[@type='text']")
private WebElement roleInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='New Role Name']//following-sibling::node()//input[@type='text']")
private WebElement newRoleInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Token Claim Name']//following-sibling::node()//input[@type='text']")
private WebElement tokenClaimNameInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Claim value']//following-sibling::node()//input[@type='text']")
private WebElement tokenClaimValueInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Claim JSON Type']//following-sibling::node()//select")
private Select claimJSONTypeInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Add to ID token']//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch addToIDTokenInput;
@FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Add to access token']//following-sibling::node()//div[@class='onoffswitch']")
private OnOffSwitch addToAccessTokenInput;
public boolean isConsentRequired() {
return consentRequiredSwitch.isOn();
}
public void setConsentRequired(boolean consentRequired) {
consentRequiredSwitch.setOn(consentRequired);
}
public String getConsentText() {
return getInputValue(consentTextElement);
}
public void setConsentText(String consentText) {
setInputValue(consentTextElement, consentText);
}
public String getMapperType() {
return mapperTypeSelect.getFirstSelectedOption().getText();
}
public void setMapperType(String type) {
mapperTypeSelect.selectByVisibleText(type);
}
public String getProperty() {
return getInputValue(propertyInput);
}
public void setProperty(String value) {
setInputValue(propertyInput, value);
}
public String getUserAttribute() {
return getInputValue(userAttributeInput);
}
public void setUserAttribute(String value) {
setInputValue(userAttributeInput, value);
}
public String getUserSessionNote() {
return getInputValue(userSessionNoteInput);
}
public void setUserSessionNote(String value) {
setInputValue(userSessionNoteInput, value);
}
public boolean isMultivalued() {
return multivaluedInput.isOn();
}
public void setMultivalued(boolean value) {
multivaluedInput.setOn(value);
}
public String getRole() {
return getInputValue(roleInput);
}
public void setRole(String value) {
setInputValue(roleInput, value);
}
public String getNewRole() {
return getInputValue(newRoleInput);
}
public void setNewRole(String value) {
setInputValue(newRoleInput, value);
}
public String getTokenClaimName() {
return getInputValue(tokenClaimNameInput);
}
public void setTokenClaimName(String value) {
setInputValue(tokenClaimNameInput, value);
}
public String getTokenClaimValue() {
return getInputValue(tokenClaimValueInput);
}
public void setTokenClaimValue(String value) {
setInputValue(tokenClaimValueInput, value);
}
public String getClaimJSONType() {
return claimJSONTypeInput.getFirstSelectedOption().getText();
}
public void setClaimJSONType(String value) {
claimJSONTypeInput.selectByVisibleText(value);
}
public boolean isAddToIDToken() {
return addToIDTokenInput.isOn();
}
public void setAddToIDToken(boolean value) {
addToIDTokenInput.setOn(value);
}
public boolean isAddToAccessToken() {
return addToAccessTokenInput.isOn();
}
public void setAddToAccessToken(boolean value) {
addToAccessTokenInput.setOn(value);
}
}

View file

@ -0,0 +1,28 @@
package org.keycloak.testsuite.console.page.federation;
import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.testsuite.console.page.AdminConsoleCreate;
/**
*
* @author pdrozd
*/
public class CreateKerberosUserProvider extends AdminConsoleCreate {
@Page
private KerberosUserProviderForm form;
public CreateKerberosUserProvider() {
setEntity("user-federation");
}
@Override
public String getUriFragment() {
return super.getUriFragment() + "/providers/kerberos";
}
public KerberosUserProviderForm form() {
return form;
}
}

View file

@ -1,5 +1,6 @@
package org.keycloak.testsuite.console.page.federation; package org.keycloak.testsuite.console.page.federation;
import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.testsuite.console.page.AdminConsoleCreate; import org.keycloak.testsuite.console.page.AdminConsoleCreate;
/** /**
@ -8,6 +9,9 @@ import org.keycloak.testsuite.console.page.AdminConsoleCreate;
*/ */
public class CreateLdapUserProvider extends AdminConsoleCreate { public class CreateLdapUserProvider extends AdminConsoleCreate {
@Page
private LdapUserProviderForm form;
public CreateLdapUserProvider() { public CreateLdapUserProvider() {
setEntity("user-federation"); setEntity("user-federation");
} }
@ -17,4 +21,7 @@ public class CreateLdapUserProvider extends AdminConsoleCreate {
return super.getUriFragment() + "/providers/ldap"; return super.getUriFragment() + "/providers/ldap";
} }
public LdapUserProviderForm form() {
return form;
}
} }

View file

@ -0,0 +1,81 @@
package org.keycloak.testsuite.console.page.federation;
import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement;
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
import org.keycloak.testsuite.page.Form;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select;
/**
* @author pdrozd
*/
public class KerberosUserProviderForm extends Form {
@FindBy(id = "consoleDisplayName")
private WebElement consoleDisplayNameInput;
@FindBy(id = "priority")
private WebElement priorityInput;
@FindBy(id = "kerberosRealm")
private WebElement kerberosRealmInput;
@FindBy(id = "serverPrincipal")
private WebElement serverPrincipalInput;
@FindBy(id = "keyTab")
private WebElement keyTabInput;
@FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='debug']]")
private OnOffSwitch debug;
@FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='allowPasswordAuthentication']]")
private OnOffSwitch allowPwdAuth;
@FindBy(id = "editMode")
private Select editModeSelect;
@FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='updateProfileFirstLogin']]")
private OnOffSwitch updateProfileFirstLogin;
public void setConsoleDisplayNameInput(String name) {
setInputValue(consoleDisplayNameInput, name);
}
public void setPriorityInput(Integer priority) {
setInputValue(priorityInput, String.valueOf(priority));
}
public void setKerberosRealmInput(String kerberosRealm) {
waitGuiForElement(By.id("kerberosRealm"));
setInputValue(kerberosRealmInput, kerberosRealm);
}
public void setServerPrincipalInput(String serverPrincipal) {
setInputValue(serverPrincipalInput, serverPrincipal);
}
public void setKeyTabInput(String keyTab) {
setInputValue(keyTabInput, keyTab);
}
public void setDebugEnabled(boolean debugEnabled) {
this.debug.setOn(debugEnabled);
}
public void setAllowPasswordAuthentication(boolean enabled) {
allowPwdAuth.setOn(enabled);
}
public void selectEditMode(String mode) {
waitGuiForElement(By.id("editMode"));
editModeSelect.selectByVisibleText(mode);
}
public void setUpdateProfileFirstLogin(boolean enabled) {
updateProfileFirstLogin.setOn(enabled);
}
}

View file

@ -1,5 +1,8 @@
package org.keycloak.testsuite.console.page.federation; package org.keycloak.testsuite.console.page.federation;
import static org.keycloak.testsuite.util.WaitUtils.waitAjaxForElement;
import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement;
import org.jboss.arquillian.graphene.findby.FindByJQuery; import org.jboss.arquillian.graphene.findby.FindByJQuery;
import org.keycloak.testsuite.console.page.fragment.OnOffSwitch; import org.keycloak.testsuite.console.page.fragment.OnOffSwitch;
import org.keycloak.testsuite.page.Form; import org.keycloak.testsuite.page.Form;
@ -8,10 +11,8 @@ import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.Select;
import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement;
/** /**
* Created by fkiss. * @author fkiss, pdrozd
*/ */
public class LdapUserProviderForm extends Form { public class LdapUserProviderForm extends Form {
@ -24,24 +25,33 @@ public class LdapUserProviderForm extends Form {
@FindBy(id = "usernameLDAPAttribute") @FindBy(id = "usernameLDAPAttribute")
private WebElement usernameLDAPAttributeInput; private WebElement usernameLDAPAttributeInput;
@FindBy(id = "rdnLDAPAttribute")
private WebElement rdnLDAPAttributeInput;
@FindBy(id = "uuidLDAPAttribute")
private WebElement uuidLDAPAttributeInput;
@FindBy(id = "userObjectClasses") @FindBy(id = "userObjectClasses")
private WebElement userObjectClassesInput; private WebElement userObjectClassesInput;
@FindBy(id = "ldapConnectionUrl") @FindBy(id = "ldapConnectionUrl")
private WebElement ldapConnectionUrlInput; private WebElement ldapConnectionUrlInput;
@FindBy(id = "ldapBaseDn")
private WebElement ldapBaseDnInput;
@FindBy(id = "ldapUsersDn") @FindBy(id = "ldapUsersDn")
private WebElement ldapUserDnInput; private WebElement ldapUserDnInput;
@FindBy(id = "authType")
private Select authTypeSelect;
@FindBy(id = "ldapBindDn") @FindBy(id = "ldapBindDn")
private WebElement ldapBindDnInput; private WebElement ldapBindDnInput;
@FindBy(id = "ldapBindCredential") @FindBy(id = "ldapBindCredential")
private WebElement ldapBindCredentialInput; private WebElement ldapBindCredentialInput;
@FindBy(id = "searchScope")
private Select searchScopeSelect;
@FindBy(id = "kerberosRealm") @FindBy(id = "kerberosRealm")
private WebElement kerberosRealmInput; private WebElement kerberosRealmInput;
@ -72,59 +82,173 @@ public class LdapUserProviderForm extends Form {
@FindByJQuery("a:contains('Test authentication')") @FindByJQuery("a:contains('Test authentication')")
private WebElement testAuthenticationButton; private WebElement testAuthenticationButton;
@FindByJQuery("div[class='onoffswitch']:eq(0)") @FindByJQuery("a:contains('Synchronize changed users')")
private WebElement synchronizeChangedUsersButton;
@FindByJQuery("button:contains('Synchronize all users')")
private WebElement synchronizeAllUsersButton;
@FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='syncRegistrations']]")
private OnOffSwitch syncRegistrations; private OnOffSwitch syncRegistrations;
@FindByJQuery("div[class='onoffswitch']:eq(1)") @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='connectionPooling']]")
private OnOffSwitch connectionPooling; private OnOffSwitch connectionPooling;
@FindByJQuery("div[class='onoffswitch']:eq(2)") @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='pagination']]")
private OnOffSwitch pagination; private OnOffSwitch pagination;
@FindByJQuery("div[class='onoffswitch']:eq(3)") @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='userAccountControlsAfterPasswordUpdate']]")
private OnOffSwitch enableAccountAfterPasswordUpdate;
@FindBy(xpath = "//div[contains(@class,'onoffswitch') and ./input[@id='allowKerberosAuthentication']]")
private OnOffSwitch allowKerberosAuth; private OnOffSwitch allowKerberosAuth;
@FindByJQuery("div[class='onoffswitch']:eq(4)") @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='debug']]")
private OnOffSwitch debug; private OnOffSwitch debug;
@FindByJQuery("div[class='onoffswitch']:eq(5)") @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='useKerberosForPasswordAuthentication']]")
private OnOffSwitch useKerberosForPwdAuth; private OnOffSwitch useKerberosForPwdAuth;
@FindByJQuery("div[class='onoffswitch']:eq(6)") @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='compositeSwitch']]")
private OnOffSwitch periodicFullSync; private OnOffSwitch periodicFullSync;
@FindByJQuery("div[class='onoffswitch']:eq(7)") @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='changedSyncEnabled']]")
private OnOffSwitch periodicChangedUsersSync; private OnOffSwitch periodicChangedUsersSync;
@FindByJQuery("button:contains('Save')") public void setConsoleDisplayNameInput(String name) {
private WebElement saveButton; setInputValue(consoleDisplayNameInput, name);
}
public void selectEditMode(String mode){ public void setPriorityInput(Integer priority) {
setInputValue(priorityInput, String.valueOf(priority));
}
public void setUsernameLDAPAttributeInput(String usernameLDAPAttribute) {
setInputValue(usernameLDAPAttributeInput, usernameLDAPAttribute);
}
public void setRdnLDAPAttributeInput(String rdnLDAPAttribute) {
setInputValue(rdnLDAPAttributeInput, rdnLDAPAttribute);
}
public void setUuidLDAPAttributeInput(String uuidLDAPAttribute) {
setInputValue(uuidLDAPAttributeInput, uuidLDAPAttribute);
}
public void setUserObjectClassesInput(String userObjectClasses) {
setInputValue(userObjectClassesInput, userObjectClasses);
}
public void setLdapConnectionUrlInput(String ldapConnectionUrl) {
setInputValue(ldapConnectionUrlInput, ldapConnectionUrl);
}
public void setLdapUserDnInput(String ldapUserDn) {
setInputValue(ldapUserDnInput, ldapUserDn);
}
public void setLdapBindDnInput(String ldapBindDn) {
setInputValue(ldapBindDnInput, ldapBindDn);
}
public void setLdapBindCredentialInput(String ldapBindCredential) {
setInputValue(ldapBindCredentialInput, ldapBindCredential);
}
public void setKerberosRealmInput(String kerberosRealm) {
waitAjaxForElement(kerberosRealmInput);
setInputValue(kerberosRealmInput, kerberosRealm);
}
public void setServerPrincipalInput(String serverPrincipal) {
waitAjaxForElement(serverPrincipalInput);
setInputValue(serverPrincipalInput, serverPrincipal);
}
public void setKeyTabInput(String keyTab) {
waitAjaxForElement(keyTabInput);
setInputValue(keyTabInput, keyTab);
}
public void setBatchSizeForSyncInput(String batchSizeForSync) {
setInputValue(batchSizeForSyncInput, batchSizeForSync);
}
public void selectEditMode(String mode) {
waitGuiForElement(By.id("editMode")); waitGuiForElement(By.id("editMode"));
editModeSelect.selectByVisibleText(mode); editModeSelect.selectByVisibleText(mode);
} }
public void selectVendor(String vendor){ public void selectVendor(String vendor) {
waitGuiForElement(By.id("editMode")); waitGuiForElement(By.id("vendor"));
vendorSelect.selectByVisibleText(vendor); vendorSelect.selectByVisibleText(vendor);
} }
public void configureLdap(String displayName, String editMode, String vendor, String connectionUrl, String userDN, String ldapBindDn, String ldapBindCredential){ public void selectAuthenticationType(String authenticationType) {
consoleDisplayNameInput.sendKeys(displayName); waitGuiForElement(By.id("authType"));
editModeSelect.selectByVisibleText(editMode); authTypeSelect.selectByVisibleText(authenticationType);
selectVendor(vendor);
ldapConnectionUrlInput.sendKeys(connectionUrl);
ldapUserDnInput.sendKeys(userDN);
ldapBindDnInput.sendKeys(ldapBindDn);
ldapBindCredentialInput.sendKeys(ldapBindCredential);
saveButton.click();
} }
public void testConnection(){ public void selectSearchScope(String searchScope) {
waitGuiForElement(By.id("searchScope"));
searchScopeSelect.selectByVisibleText(searchScope);
}
public void setSyncRegistrationsEnabled(boolean syncRegistrationsEnabled) {
this.syncRegistrations.setOn(syncRegistrationsEnabled);
}
public void setConnectionPoolingEnabled(boolean connectionPoolingEnabled) {
this.connectionPooling.setOn(connectionPoolingEnabled);
}
public void setPaginationEnabled(boolean paginationEnabled) {
this.pagination.setOn(paginationEnabled);
}
public void setAccountAfterPasswordUpdateEnabled(boolean enabled) {
if ((!enableAccountAfterPasswordUpdate.isOn() && enabled)
|| !enabled && enableAccountAfterPasswordUpdate.isOn()) {
driver.findElement(By
.xpath("//div[contains(@class,'onoffswitch') and ./input[@id='userAccountControlsAfterPasswordUpdate']]"))
.findElements(By.tagName("span")).get(0).click();
}
}
public void setAllowKerberosAuthEnabled(boolean enabled) {
if ((!allowKerberosAuth.isOn() && enabled) || !enabled && allowKerberosAuth.isOn()) {
driver.findElement(
By.xpath("//div[contains(@class,'onoffswitch') and ./input[@id='allowKerberosAuthentication']]"))
.findElements(By.tagName("span")).get(0).click();
}
}
public void setDebugEnabled(boolean debugEnabled) {
this.debug.setOn(debugEnabled);
}
public void setUseKerberosForPwdAuthEnabled(boolean useKerberosForPwdAuthEnabled) {
this.useKerberosForPwdAuth.setOn(useKerberosForPwdAuthEnabled);
}
public void setPeriodicFullSyncEnabled(boolean periodicFullSyncEnabled) {
this.periodicFullSync.setOn(periodicFullSyncEnabled);
}
public void setPeriodicChangedUsersSyncEnabled(boolean periodicChangedUsersSyncEnabled) {
this.periodicChangedUsersSync.setOn(periodicChangedUsersSyncEnabled);
}
public void testConnection() {
testConnectionButton.click(); testConnectionButton.click();
} }
public void testAuthentication(){ public void testAuthentication() {
testAuthenticationButton.click(); testAuthenticationButton.click();
} }
public void synchronizeAllUsers() {
waitAjaxForElement(synchronizeAllUsersButton);
synchronizeAllUsersButton.click();
}
} }

View file

@ -36,6 +36,14 @@ public class OnOffSwitch {
@ArquillianResource @ArquillianResource
private Actions actions; private Actions actions;
public OnOffSwitch() {
}
public OnOffSwitch(WebElement root, Actions actions) {
this.root = root;
this.actions = actions;
}
public boolean isOn() { public boolean isOn() {
waitAjaxForElement(root); waitAjaxForElement(root);
return root.findElement(By.tagName("input")).isSelected(); return root.findElement(By.tagName("input")).isSelected();

View file

@ -41,6 +41,11 @@ public final class WaitUtils {
.element(element).is().not().present(); .element(element).is().not().present();
} }
public static void waitAjaxForElementNotVisible(WebElement element) {
waitAjax().until()
.element(element).is().not().visible();
}
public static void waitGuiForElement(By element, String message) { public static void waitGuiForElement(By element, String message) {
waitGui().until(message) waitGui().until(message)
.element(element).is().present(); .element(element).is().present();

View file

@ -24,6 +24,7 @@ package org.keycloak.testsuite.console.authentication;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.console.AbstractConsoleTest; import org.keycloak.testsuite.console.AbstractConsoleTest;
@ -66,42 +67,58 @@ public class OTPPolicyTest extends AbstractConsoleTest {
} }
@Test @Test
@Ignore //KEYCLOAK-2051 when you close notification, it is not displayed again
public void invalidValuesTest() { public void invalidValuesTest() {
otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "", "30"); otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "", "30");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.navigateTo();// workaround: input.clear() doesn't work when <input type="number" ...
otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, " ", "30"); otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, " ", "30");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.navigateTo();
otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "no number", "30"); otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "no number", "30");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.navigateTo();
RealmRepresentation realm = testRealmResource().toRepresentation(); RealmRepresentation realm = testRealmResource().toRepresentation();
assertEquals(Integer.valueOf(1), realm.getOtpPolicyLookAheadWindow()); assertEquals(Integer.valueOf(1), realm.getOtpPolicyLookAheadWindow());
otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", ""); otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.navigateTo();
otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " "); otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " ");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.navigateTo();
otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number"); otpPolicyPage.form().setValues(OTPType.TIME_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.navigateTo();
realm = testRealmResource().toRepresentation(); realm = testRealmResource().toRepresentation();
assertEquals(Integer.valueOf(30), realm.getOtpPolicyPeriod()); assertEquals(Integer.valueOf(30), realm.getOtpPolicyPeriod());
otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", ""); otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.navigateTo();
otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " "); otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", " ");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.navigateTo();
otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number"); otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "no number");
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage()); assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
otpPolicyPage.closeNotification();
otpPolicyPage.form().setValues(OTPType.COUNTER_BASED, OTPHashAlg.SHA1, Digits.EMPTY, "1", "1 2"); otpPolicyPage.navigateTo();
assertEquals("Error! Missing or invalid field(s). Please verify the fields in red.", otpPolicyPage.getErrorMessage());
realm = testRealmResource().toRepresentation(); realm = testRealmResource().toRepresentation();
assertEquals(Integer.valueOf(0), realm.getOtpPolicyInitialCounter()); assertEquals(Integer.valueOf(0), realm.getOtpPolicyInitialCounter());

View file

@ -0,0 +1,75 @@
package org.keycloak.testsuite.console.federation;
import static org.junit.Assert.assertEquals;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.testsuite.console.AbstractConsoleTest;
import org.keycloak.testsuite.console.page.federation.CreateKerberosUserProvider;
/**
* @author pdrozd
*/
public class KerberosUserFederationTest extends AbstractConsoleTest {
private static final String UNSYNCED = "UNSYNCED";
private static final String READ_ONLY = "READ_ONLY";
@Page
private CreateKerberosUserProvider createKerberosUserProvider;
@Test
public void configureKerberosProvider() {
createKerberosUserProvider.navigateTo();
createKerberosUserProvider.form().setConsoleDisplayNameInput("kerberos");
createKerberosUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG");
createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");
createKerberosUserProvider.form().setKeyTabInput("http.keytab");
createKerberosUserProvider.form().setDebugEnabled(true);
createKerberosUserProvider.form().setAllowPasswordAuthentication(true);
createKerberosUserProvider.form().selectEditMode(READ_ONLY);
createKerberosUserProvider.form().setUpdateProfileFirstLogin(true);
createKerberosUserProvider.form().save();
assertFlashMessageSuccess();
RealmRepresentation realm = testRealmResource().toRepresentation();
UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0);
assertKerberosSetings(ufpr, "KEYCLOAK.ORG", "HTTP/localhost@KEYCLOAK.ORG", "http.keytab", "true", "true", "true");
}
@Test
public void invalidSettingsTest() {
createKerberosUserProvider.navigateTo();
createKerberosUserProvider.form().setConsoleDisplayNameInput("kerberos");
createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");
createKerberosUserProvider.form().setKeyTabInput("http.keytab");
createKerberosUserProvider.form().setDebugEnabled(true);
createKerberosUserProvider.form().setAllowPasswordAuthentication(true);
createKerberosUserProvider.form().selectEditMode(UNSYNCED);
createKerberosUserProvider.form().setUpdateProfileFirstLogin(true);
createKerberosUserProvider.form().save();
assertFlashMessageDanger();
createKerberosUserProvider.form().setServerPrincipalInput("");
createKerberosUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG");;
createKerberosUserProvider.form().save();
assertFlashMessageDanger();
createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");;
createKerberosUserProvider.form().setKeyTabInput("");
createKerberosUserProvider.form().save();
assertFlashMessageDanger();
createKerberosUserProvider.form().setKeyTabInput("http.keytab");;
createKerberosUserProvider.form().save();
assertFlashMessageSuccess();
}
private void assertKerberosSetings(UserFederationProviderRepresentation ufpr, String kerberosRealm, String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication, String updateProfileFirstLogin) {
assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm"));
assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal"));
assertEquals(keyTab, ufpr.getConfig().get("keyTab"));
assertEquals(debug, ufpr.getConfig().get("debug"));
assertEquals(useKerberosForPasswordAuthentication, ufpr.getConfig().get("allowKerberosAuthentication"));
assertEquals(updateProfileFirstLogin, ufpr.getConfig().get("updateProfileFirstLogin"));
}
}

View file

@ -1,71 +1,192 @@
package org.keycloak.testsuite.console.federation; package org.keycloak.testsuite.console.federation;
import static org.junit.Assert.assertEquals;
import java.util.Properties;
import org.jboss.arquillian.graphene.page.Page; import org.jboss.arquillian.graphene.page.Page;
import org.junit.*; import org.junit.Test;
import org.keycloak.models.LDAPConstants; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.console.AbstractConsoleTest; import org.keycloak.testsuite.console.AbstractConsoleTest;
import org.keycloak.testsuite.console.page.federation.LdapUserProviderForm; import org.keycloak.testsuite.console.page.federation.CreateLdapUserProvider;
import org.keycloak.testsuite.console.page.federation.UserFederation; import org.keycloak.util.ldap.LDAPEmbeddedServer;
import org.keycloak.testsuite.console.page.users.Users;
import org.keycloak.testsuite.util.LDAPTestConfiguration;
import java.util.Map;
import static org.junit.Assert.assertTrue;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
import static org.keycloak.testsuite.admin.Users.setPasswordFor;
/** /**
* Created by fkiss. * @author fkiss, pdrozd
*/ */
public class LdapUserFederationTest extends AbstractConsoleTest { public class LdapUserFederationTest extends AbstractConsoleTest {
@Page private static final String UNSYNCED = "UNSYNCED";
private LdapUserProviderForm ldapUserProviderForm;
private static final String READ_ONLY = "READ_ONLY";
private static final String RED_HAT_DIRECTORY_SERVER = "Red Hat Directory Server";
private static final String WRITABLE = "WRITABLE";
private static final String ACTIVE_DIRECTORY = "Active Directory";
@Page @Page
private UserFederation userFederationPage; private CreateLdapUserProvider createLdapUserProvider;
@Page @Test
private Users usersPage; public void configureAdProvider() {
createLdapUserProvider.navigateTo();
createLdapUserProvider.form().selectVendor(ACTIVE_DIRECTORY);
createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
createLdapUserProvider.form().selectEditMode(WRITABLE);
createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389");
createLdapUserProvider.form().setLdapBindDnInput("KEYCLOAK/Administrator");
createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
createLdapUserProvider.form().setAccountAfterPasswordUpdateEnabled(false);
// enable kerberos
createLdapUserProvider.form().setAllowKerberosAuthEnabled(true);
createLdapUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG");
createLdapUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");
createLdapUserProvider.form().setKeyTabInput("http.keytab");
createLdapUserProvider.form().setDebugEnabled(true);
createLdapUserProvider.form().save();
assertFlashMessageSuccess();
@Before RealmRepresentation realm = testRealmResource().toRepresentation();
public void beforeTestLdapUserFederation() { UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0);
//configure().userFederation(); assertLdapProviderSetting(ufpr, "ldap", 0, WRITABLE, "false", "ad", "1", "true", "true", "false");
assertLdapBasicMapping(ufpr, "cn", "cn", "objectGUID", "person, organizationalPerson, user",
"ou=People,dc=keycloak,dc=org");
assertLdapSyncSetings(ufpr, "1000", 0, 0);
assertLdapKerberosSetings(ufpr, "KEYCLOAK.ORG", "HTTP/localhost@KEYCLOAK.ORG", "http.keytab", "true", "false");
} }
@Ignore
@Test @Test
public void addAndConfigureProvider() { public void configureRhdsProvider() {
adminConsolePage.navigateTo(); createLdapUserProvider.navigateTo();
testRealmLoginPage.form().login(testUser); createLdapUserProvider.form().selectVendor(RED_HAT_DIRECTORY_SERVER);
createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
createLdapUserProvider.form().selectEditMode(READ_ONLY);
createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389");
createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system");
createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
createLdapUserProvider.form().save();
assertFlashMessageSuccess();
String name = "ldapname"; RealmRepresentation realm = testRealmResource().toRepresentation();
UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0);
String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties"; assertLdapProviderSetting(ufpr, "ldap", 0, READ_ONLY, "false", "rhds", "1", "true", "true", "true");
LDAPTestConfiguration ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(LDAP_CONNECTION_PROPERTIES_LOCATION); assertLdapBasicMapping(ufpr, "uid", "uid", "nsuniqueid", "inetOrgPerson, organizationalPerson",
"ou=People,dc=keycloak,dc=org");
UserRepresentation newUser = new UserRepresentation(); assertLdapSyncSetings(ufpr, "1000", 0, 0);
String testUsername = "defaultrole tester";
newUser.setUsername(testUsername);
setPasswordFor(newUser, PASSWORD);
Map<String,String> ldapConfig = ldapTestConfiguration.getLDAPConfig();
//addLdapProviderTest
configure().userFederation();
userFederationPage.addProvider("ldap");
ldapUserProviderForm.configureLdap(ldapConfig.get(LDAPConstants.LDAP_PROVIDER), ldapConfig.get(LDAPConstants.EDIT_MODE), ldapConfig.get(LDAPConstants.VENDOR), ldapConfig.get(LDAPConstants.CONNECTION_URL), ldapConfig.get(LDAPConstants.USERS_DN), ldapConfig.get(LDAPConstants.BIND_DN), ldapConfig.get(LDAPConstants.BIND_CREDENTIAL));
} }
@Ignore
@Test @Test
public void caseSensitiveSearch() { public void invalidSettingsTest() {
// This should fail for now due to case-sensitivity createLdapUserProvider.navigateTo();
adminConsolePage.navigateTo(); createLdapUserProvider.form().selectVendor(ACTIVE_DIRECTORY);
testRealmLoginPage.form().login("johnKeycloak", "Password1"); createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
assertTrue(flashMessage.getText(), flashMessage.isDanger()); createLdapUserProvider.form().selectEditMode(UNSYNCED);
createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system");
createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
createLdapUserProvider.form().save();
assertFlashMessageDanger();
createLdapUserProvider.form().setLdapUserDnInput("");
createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389");
createLdapUserProvider.form().save();
assertFlashMessageDanger();
createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
createLdapUserProvider.form().setLdapBindDnInput("");
createLdapUserProvider.form().save();
assertFlashMessageDanger();
createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system");
createLdapUserProvider.form().setLdapBindCredentialInput("");
createLdapUserProvider.form().save();
assertFlashMessageDanger();
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
createLdapUserProvider.form().save();
assertFlashMessageSuccess();
}
@Test
public void testConnection() throws Exception {
createLdapUserProvider.navigateTo();
createLdapUserProvider.form().selectVendor("Other");
createLdapUserProvider.form().setConsoleDisplayNameInput("ldap");
createLdapUserProvider.form().selectEditMode(WRITABLE);
createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:10389");
createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system");
createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org");
createLdapUserProvider.form().setLdapBindCredentialInput("secret");
createLdapUserProvider.form().setAccountAfterPasswordUpdateEnabled(true);
createLdapUserProvider.form().save();
assertFlashMessageSuccess();
LDAPEmbeddedServer ldapServer = null;
try {
ldapServer = startEmbeddedLdapServer();
createLdapUserProvider.form().testConnection();
assertFlashMessageSuccess();
createLdapUserProvider.form().testAuthentication();
assertFlashMessageSuccess();
createLdapUserProvider.form().synchronizeAllUsers();
assertFlashMessageSuccess();
createLdapUserProvider.form().setLdapBindCredentialInput("secret1");
createLdapUserProvider.form().testAuthentication();
assertFlashMessageDanger();
} finally {
if (ldapServer != null) {
ldapServer.stop();
}
}
}
private void assertLdapProviderSetting(UserFederationProviderRepresentation ufpr, String name, int priority,
String editMode, String syncRegistrations, String vendor, String searchScope, String connectionPooling,
String pagination, String enableAccountAfterPasswordUpdate) {
assertEquals(name, ufpr.getDisplayName());
assertEquals(priority, ufpr.getPriority());
assertEquals(editMode, ufpr.getConfig().get("editMode"));
assertEquals(syncRegistrations, ufpr.getConfig().get("syncRegistrations"));
assertEquals(vendor, ufpr.getConfig().get("vendor"));
assertEquals(searchScope, ufpr.getConfig().get("searchScope"));
assertEquals(connectionPooling, ufpr.getConfig().get("connectionPooling"));
assertEquals(pagination, ufpr.getConfig().get("pagination"));
assertEquals(enableAccountAfterPasswordUpdate, ufpr.getConfig().get("userAccountControlsAfterPasswordUpdate"));
}
private void assertLdapBasicMapping(UserFederationProviderRepresentation ufpr, String usernameLdapAttribute,
String rdnLdapAttr, String uuidLdapAttr, String userObjectClasses, String userDN) {
assertEquals(usernameLdapAttribute, ufpr.getConfig().get("usernameLDAPAttribute"));
assertEquals(rdnLdapAttr, ufpr.getConfig().get("rdnLDAPAttribute"));
assertEquals(uuidLdapAttr, ufpr.getConfig().get("uuidLDAPAttribute"));
assertEquals(userObjectClasses, ufpr.getConfig().get("userObjectClasses"));
assertEquals(userDN, ufpr.getConfig().get("usersDn"));
}
private void assertLdapKerberosSetings(UserFederationProviderRepresentation ufpr, String kerberosRealm,
String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication) {
assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm"));
assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal"));
assertEquals(keyTab, ufpr.getConfig().get("keyTab"));
assertEquals(debug, ufpr.getConfig().get("debug"));
assertEquals(useKerberosForPasswordAuthentication,
ufpr.getConfig().get("useKerberosForPasswordAuthentication"));
}
private void assertLdapSyncSetings(UserFederationProviderRepresentation ufpr, String batchSize,
int periodicFullSync, int periodicChangedUsersSync) {
assertEquals(batchSize, ufpr.getConfig().get("batchSizeForSync"));
assertEquals(periodicFullSync, ufpr.getFullSyncPeriod());
assertEquals(periodicChangedUsersSync, ufpr.getChangedSyncPeriod());
}
private LDAPEmbeddedServer startEmbeddedLdapServer() throws Exception {
Properties defaultProperties = new Properties();
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY);
defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:ldap/users.ldif");
LDAPEmbeddedServer ldapEmbeddedServer = new LDAPEmbeddedServer(defaultProperties);
ldapEmbeddedServer.init();
ldapEmbeddedServer.start();
return ldapEmbeddedServer;
} }
} }

View file

@ -0,0 +1,20 @@
dn: dc=keycloak,dc=org
objectclass: dcObject
objectclass: organization
o: Keycloak
dc: Keycloak
dn: ou=People,dc=keycloak,dc=org
objectclass: top
objectclass: organizationalUnit
ou: People
dn: ou=RealmRoles,dc=keycloak,dc=org
objectclass: top
objectclass: organizationalUnit
ou: RealmRoles
dn: ou=FinanceRoles,dc=keycloak,dc=org
objectclass: top
objectclass: organizationalUnit
ou: FinanceRoles