keycloak-aplcache
Changes
core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java 3(+2 -1)
distribution/adapters/pom.xml 1(+1 -0)
distribution/adapters/wf8-adapter/pom.xml 20(+20 -0)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/net/iharder/base64/main/module.xml 13(+13 -0)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/apache/httpcomponents/4.3/module.xml 14(+14 -0)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml 20(+20 -0)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml 21(+21 -0)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml 17(+17 -0)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml 25(+25 -0)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wf8-subsystem/main/module.xml 48(+48 -0)
distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml 26(+26 -0)
examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java 11(+4 -7)
examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java 7(+2 -5)
examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java 6(+3 -3)
federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java 15(+7 -8)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java 40(+40 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java 42(+37 -5)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java 1(+0 -1)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java 93(+88 -5)
federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java 45(+40 -5)
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js 129(+127 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-generic.html 9(+7 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-kerberos.html 5(+5 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html 5(+5 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html 78(+78 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mappers.html 53(+53 -0)
integration/wildfly/pom.xml 1(+1 -0)
integration/wildfly/wf8-subsystem/pom.xml 115(+115 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java 47(+47 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java 61(+61 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java 50(+50 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java 42(+42 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java 131(+131 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java 179(+179 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java 67(+67 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java 41(+41 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java 85(+85 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java 63(+63 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java 45(+45 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java 224(+224 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java 66(+66 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java 87(+87 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java 41(+41 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java 54(+54 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java 61(+61 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java 130(+130 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java 41(+41 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java 59(+59 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java 228(+228 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakLogger.java 45(+45 -0)
integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakMessages.java 34(+34 -0)
integration/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension 1(+1 -0)
integration/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties 72(+72 -0)
integration/wildfly/wf8-subsystem/src/main/resources/subsystem-templates/keycloak-adapter.xml 7(+7 -0)
integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java 86(+86 -0)
integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java 63(+63 -0)
integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml 24(+24 -0)
pom.xml 12(+11 -1)
services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java 298(+298 -0)
services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java 140(+20 -120)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java 6(+3 -3)
testsuite/integration-arquillian/pom.xml 182(+182 -0)
testsuite/integration-arquillian/README.md 21(+21 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/AbstractKeyCloakTest.java 48(+48 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/AbstractTest.java 73(+73 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/CreateRealm.java 52(+52 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/FlashMessage.java 67(+67 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/MenuPage.java 81(+81 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/Navigation.java 157(+157 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/OnOffSwitch.java 64(+64 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/PickList.java 62(+62 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Account.java 93(+93 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Client.java 104(+104 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/PasswordPolicy.java 39(+39 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Provider.java 40(+40 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/SocialProvider.java 39(+39 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Theme.java 40(+40 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/User.java 146(+146 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/UserAction.java 40(+40 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/AbstractPage.java 54(+54 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/AccountPage.java 107(+107 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/PasswordPage.java 82(+82 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/LoginPage.java 67(+67 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/RegisterPage.java 99(+99 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/session/SessionsPage.java 32(+32 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/ClientPage.java 129(+129 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/CredentialsPage.java 71(+71 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/DefaultRolesPage.java 55(+55 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/GeneralSettingsPage.java 80(+80 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/LoginSettingsPage.java 63(+63 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/RolesPage.java 112(+112 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/SecurityPage.java 109(+109 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/SocialSettingsPage.java 88(+88 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/ThemesSettingsPage.java 80(+80 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/TokensPage.java 65(+65 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/UserPage.java 195(+195 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/account/AccountManagementTest.java 121(+121 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/client/AddNewClientTest.java 109(+109 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/role/AddNewRoleTest.java 94(+94 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/session/SessionsTest.java 44(+44 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/session/TokensTest.java 70(+70 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/CredentialsTest.java 43(+43 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/SecuritySettingsTest.java 38(+38 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/SocialSettingsTest.java 66(+66 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/ThemesSettingsTest.java 56(+56 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/user/AddNewUserTest.java 116(+116 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/user/RegisterNewUserTest.java 132(+132 -0)
testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/Constants.java 33(+33 -0)
Details
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java
index f03b376..f7f594a 100644
--- a/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java
@@ -1,5 +1,6 @@
package org.keycloak.representations.idm;
+import java.util.LinkedList;
import java.util.List;
/**
@@ -11,7 +12,7 @@ public class UserFederationMapperTypeRepresentation {
protected String category;
protected String helpText;
- protected List<ConfigPropertyRepresentation> properties;
+ protected List<ConfigPropertyRepresentation> properties = new LinkedList<>();
public String getId() {
return id;
distribution/adapters/pom.xml 1(+1 -0)
diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml
index 612ecae..8c13893 100755
--- a/distribution/adapters/pom.xml
+++ b/distribution/adapters/pom.xml
@@ -38,5 +38,6 @@
<module>tomcat7-adapter-zip</module>
<module>tomcat8-adapter-zip</module>
<module>wildfly-adapter-zip</module>
+ <module>wf8-adapter</module>
</modules>
</project>
distribution/adapters/wf8-adapter/pom.xml 20(+20 -0)
diff --git a/distribution/adapters/wf8-adapter/pom.xml b/distribution/adapters/wf8-adapter/pom.xml
new file mode 100644
index 0000000..7f71d64
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/pom.xml
@@ -0,0 +1,20 @@
+<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/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.3.0.Final-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+ <name>Keycloak Wildfly 8 Adapter</name>
+ <description/>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-wf8-adapter-dist-pom</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>wf8-modules</module>
+ <module>wf8-adapter-zip</module>
+ </modules>
+</project>
diff --git a/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml b/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml
new file mode 100755
index 0000000..cad94c5
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml
@@ -0,0 +1,29 @@
+<assembly>
+ <id>war-dist</id>
+
+ <formats>
+ <format>zip</format>
+ <format>tar.gz</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <fileSets>
+ <fileSet>
+ <directory>${project.build.directory}/unpacked</directory>
+ <includes>
+ <include>net/iharder/base64/**</include>
+ <include>org/apache/httpcomponents/**</include>
+ <include>org/keycloak/keycloak-core/**</include>
+ <include>org/keycloak/keycloak-adapter-core/**</include>
+ <include>org/keycloak/keycloak-jboss-adapter-core/**</include>
+ <include>org/keycloak/keycloak-undertow-adapter/**</include>
+ <include>org/keycloak/keycloak-wildfly-adapter/**</include>
+ <include>org/keycloak/keycloak-wf8-subsystem/**</include>
+ </includes>
+ <excludes>
+ <exclude>**/*.war</exclude>
+ </excludes>
+ <outputDirectory>modules/system/layers/base</outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml b/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml
new file mode 100755
index 0000000..aa33dd3
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml
@@ -0,0 +1,76 @@
+<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/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.3.0.Final-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-wf8-adapter-dist</artifactId>
+ <packaging>pom</packaging>
+ <name>Keycloak Wildfly 8 Adapter Distro</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wf8-modules</artifactId>
+ <type>zip</type>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wf8-modules</artifactId>
+ <type>zip</type>
+ <outputDirectory>${project.build.directory}/unpacked</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>assemble</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>assembly.xml</descriptor>
+ </descriptors>
+ <outputDirectory>
+ target
+ </outputDirectory>
+ <workDirectory>
+ target/assembly/work
+ </workDirectory>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/assembly.xml b/distribution/adapters/wf8-adapter/wf8-modules/assembly.xml
new file mode 100755
index 0000000..4a34435
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/assembly.xml
@@ -0,0 +1,22 @@
+<assembly>
+ <id>dist</id>
+
+ <formats>
+ <format>zip</format>
+ </formats>
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <fileSets>
+ <fileSet>
+ <directory>../../</directory>
+ <includes>
+ <include>License.html</include>
+ </includes>
+ <outputDirectory></outputDirectory>
+ </fileSet>
+ <fileSet>
+ <directory>${project.build.directory}/modules</directory>
+ <outputDirectory></outputDirectory>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/build.xml b/distribution/adapters/wf8-adapter/wf8-modules/build.xml
new file mode 100755
index 0000000..0ede555
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/build.xml
@@ -0,0 +1,88 @@
+<!--
+ ~ JBoss, Home of Professional Open Source.
+ ~ Copyright 2012, Red Hat, Inc., and individual contributors
+ ~ as indicated by the @author tags. See the copyright.txt file in the
+ ~ distribution for a full listing of individual contributors.
+ ~
+ ~ This is free software; you can redistribute it and/or modify it
+ ~ under the terms of the GNU Lesser General Public License as
+ ~ published by the Free Software Foundation; either version 2.1 of
+ ~ the License, or (at your option) any later version.
+ ~
+ ~ This software is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ~ Lesser General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU Lesser General Public
+ ~ License along with this software; if not, write to the Free
+ ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ -->
+
+<project name="module-repository" basedir="." default="all">
+
+ <import file="lib.xml"/>
+
+ <property name="output.dir" value="target"/>
+
+ <target name="all">
+ <antcall target="modules">
+ <param name="mavenized.modules" value="false"/>
+ <param name="output.dir" value="target"/>
+ </antcall>
+ </target>
+
+
+ <target name="modules">
+
+ <!-- server min dependencies -->
+
+ <module-def name="org.keycloak.keycloak-core">
+ <maven-resource group="org.keycloak" artifact="keycloak-core"/>
+ </module-def>
+
+ <module-def name="net.iharder.base64">
+ <maven-resource group="net.iharder" artifact="base64"/>
+ </module-def>
+
+
+ <!-- subsystems -->
+
+ <module-def name="org.keycloak.keycloak-adapter-core">
+ <maven-resource group="org.keycloak" artifact="keycloak-adapter-core"/>
+ </module-def>
+
+ <module-def name="org.keycloak.keycloak-jboss-adapter-core">
+ <maven-resource group="org.keycloak" artifact="keycloak-jboss-adapter-core"/>
+ </module-def>
+
+ <module-def name="org.keycloak.keycloak-undertow-adapter">
+ <maven-resource group="org.keycloak" artifact="keycloak-undertow-adapter"/>
+ </module-def>
+
+ <module-def name="org.keycloak.keycloak-wildfly-adapter">
+ <maven-resource group="org.keycloak" artifact="keycloak-wildfly-adapter"/>
+ </module-def>
+
+ <module-def name="org.keycloak.keycloak-wf8-subsystem">
+ <maven-resource group="org.keycloak" artifact="keycloak-wf8-subsystem"/>
+ </module-def>
+
+ <module-def name="org.apache.httpcomponents" slot="4.3">
+ <maven-resource group="org.apache.httpcomponents" artifact="httpclient"/>
+ <maven-resource group="org.apache.httpcomponents" artifact="httpcore"/>
+ <maven-resource group="org.apache.httpcomponents" artifact="httpmime"/>
+ </module-def>
+
+ </target>
+
+ <target name="clean-target">
+ <delete dir="${output.dir}"/>
+ </target>
+
+ <target name="clean" depends="clean-target">
+ <delete file="maven-ant-tasks.jar"/>
+ </target>
+
+</project>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/lib.xml b/distribution/adapters/wf8-adapter/wf8-modules/lib.xml
new file mode 100755
index 0000000..3d9438a
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/lib.xml
@@ -0,0 +1,282 @@
+<!--
+ ~ JBoss, Home of Professional Open Source.
+ ~ Copyright 2010, Red Hat, Inc., and individual contributors
+ ~ as indicated by the @author tags. See the copyright.txt file in the
+ ~ distribution for a full listing of individual contributors.
+ ~
+ ~ This is free software; you can redistribute it and/or modify it
+ ~ under the terms of the GNU Lesser General Public License as
+ ~ published by the Free Software Foundation; either version 2.1 of
+ ~ the License, or (at your option) any later version.
+ ~
+ ~ This software is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ~ Lesser General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU Lesser General Public
+ ~ License along with this software; if not, write to the Free
+ ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ -->
+
+<project name="module-repository-lib">
+
+ <property name="src.dir" value="src"/>
+ <property name="module.repo.src.dir" value="${src.dir}/main/resources/modules"/>
+ <property name="module.xml" value="module.xml"/>
+
+ <taskdef resource="net/sf/antcontrib/antlib.xml"/>
+ <taskdef name="jandex" classname="org.jboss.jandex.JandexAntTask" />
+
+ <macrodef name="module-def">
+ <attribute name="name"/>
+ <attribute name="slot" default="main"/>
+ <element name="resources" implicit="yes" optional="yes"/>
+
+ <sequential>
+ <echo message="Initializing module -> @{name}"/>
+ <property name="module.repo.output.dir" value="${output.dir}/modules"/>
+ <!-- Figure out the correct module path -->
+ <define-module-dir name="@{name}" slot="@{slot}"/>
+
+ <!-- Make the module output director -->
+ <mkdir dir="${module.repo.output.dir}/${current.module.path}"/>
+
+ <!-- Copy the module.xml and other stuff to the output director -->
+ <copy todir="${module.repo.output.dir}/${current.module.path}" overwrite="true">
+ <fileset dir="${module.repo.src.dir}/${current.module.path}">
+ <include name="**"/>
+ </fileset>
+ </copy>
+
+ <!-- Process the resource -->
+ <resources/>
+
+ <!-- Add keycloak version property to module xml -->
+ <replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}"
+ token="$${project.version}"
+ value="${project.version}"/>
+
+ <!-- Some final cleanup -->
+ <replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}">
+ <replacetoken>
+ <![CDATA[
+ <!-- Insert resources here -->]]></replacetoken>
+ <replacevalue>
+ </replacevalue>
+ </replace>
+
+ </sequential>
+ </macrodef>
+
+ <macrodef name="bundle-def">
+ <attribute name="name"/>
+ <attribute name="slot" default="main"/>
+ <element name="resources" implicit="yes" optional="yes"/>
+
+ <sequential>
+ <echo message="Initializing bundle -> @{name}"/>
+ <property name="bundle.repo.output.dir" value="${output.dir}/bundles/system/layers/base"/>
+ <!-- Figure out the correct bundle path -->
+ <define-bundle-dir name="@{name}" slot="@{slot}" />
+
+ <!-- Make the bundle output director -->
+ <mkdir dir="${bundle.repo.output.dir}/${current.bundle.path}"/>
+
+ <!-- Process the resource -->
+ <resources/>
+
+ </sequential>
+ </macrodef>
+
+ <macrodef name="maven-bundle" >
+ <attribute name="group"/>
+ <attribute name="artifact"/>
+
+ <sequential>
+ <!-- Copy the jar to the bundle dir -->
+ <property name="bundle.repo.output.dir" value="${output.dir}/bundles/system/layers/base"/>
+ <copy todir="${bundle.repo.output.dir}/${current.bundle.path}" failonerror="true">
+ <fileset file="${@{group}:@{artifact}:jar}"/>
+ <mapper type="flatten" />
+ </copy>
+ </sequential>
+ </macrodef>
+
+ <scriptdef name="define-module-dir" language="javascript" manager="bsf">
+ <attribute name="name"/>
+ <attribute name="slot"/>
+ <![CDATA[
+ name = attributes.get("name");
+ name = name.replace(".", "/");
+ project.setProperty("current.module.path", name + "/" + attributes.get("slot"));
+ ]]>
+ </scriptdef>
+
+ <scriptdef name="define-bundle-dir" language="javascript" manager="bsf">
+ <attribute name="name"/>
+ <attribute name="slot"/>
+ <![CDATA[
+ name = attributes.get("name");
+ name = name.replace(".", "/");
+ project.setProperty("current.bundle.path", name + "/" + attributes.get("slot"));
+ ]]>
+ </scriptdef>
+
+ <!--
+ Get the version from the parent directory of the jar. If the parent directory is 'target' this
+ means that the jar is contained in AS build so extract the version from the file name
+ -->
+ <scriptdef name="define-maven-artifact" language="javascript" manager="bsf">
+ <attribute name="group"/>
+ <attribute name="artifact"/>
+ <attribute name="classifier"/>
+ <attribute name="element"/>
+ <attribute name="path"/>
+ <![CDATA[
+ importClass(Packages.java.io.File);
+ group = attributes.get("group");
+ artifact = attributes.get("artifact");
+ classifier = attributes.get("classifier");
+ element = attributes.get("element");
+ path = attributes.get("path");
+ if(path.indexOf('${') != -1) {
+ throw "Module resource root not found, make sure it is listed in build/pom.xml" + path;
+ }
+ fp = new File(path);
+ version = fp.getParentFile().getName();
+ if (version.equals("target")) {
+ version = fp.getName();
+ version = version.substring(artifact.length() + 1);
+ suffix = ".jar";
+ if (classifier) {
+ suffix = "-" + classifier + suffix;
+ }
+ version = version.replace(suffix, "");
+ }
+
+ root = "<" + element + " name=\"" + group + ":" + artifact + ":" + version;
+ if (classifier) {
+ root = root + ":" + classifier;
+ }
+ root = root + "\"/>";
+ project.setProperty("current.maven.root", root);
+ ]]>
+ </scriptdef>
+
+ <macrodef name="maven-resource" >
+ <attribute name="group"/>
+ <attribute name="artifact"/>
+ <attribute name="jandex" default="false" />
+
+ <sequential>
+ <if>
+ <equals arg1="${mavenized.modules}" arg2="true"/>
+ <then>
+ <define-maven-artifact group="@{group}" artifact="@{artifact}" element="artifact" path="${@{group}:@{artifact}:jar}"/>
+ <replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}">
+ <replacefilter token="<!-- Insert resources here -->" value="${current.maven.root} <!-- Insert resources here -->"/>
+ </replace>
+ </then>
+
+ <else>
+ <!-- Copy the jar to the module dir -->
+ <copy todir="${module.repo.output.dir}/${current.module.path}" failonerror="true">
+ <fileset file="${@{group}:@{artifact}:jar}"/>
+ <mapper type="flatten" />
+ </copy>
+
+ <basename file="${@{group}:@{artifact}:jar}" property="resourcename.@{group}.@{artifact}"/>
+ <!-- Generate the Jandex Index -->
+ <jandex run="@{jandex}" newJar="true" >
+ <fileset dir="${module.repo.output.dir}/${current.module.path}" />
+ </jandex>
+ <!-- Update the resource entry in module.xml -->
+ <define-resource-root path="${resourcename.@{group}.@{artifact}}" jandex="@{jandex}"/>
+ <replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}">
+ <replacefilter token="<!-- Insert resources here -->" value="${current.resource.root} <!-- Insert resources here -->"/>
+ </replace>
+ </else>
+ </if>
+ </sequential>
+ </macrodef>
+
+
+
+ <macrodef name="maven-resource-with-classifier" >
+ <attribute name="group"/>
+ <attribute name="artifact"/>
+ <attribute name="classifier"/>
+ <attribute name="jandex" default="false" />
+
+ <sequential>
+ <if>
+ <equals arg1="${mavenized.modules}" arg2="true"/>
+ <then>
+ <define-maven-artifact group="@{group}" artifact="@{artifact}" element="artifact" classifier="@{classifier}" path="${@{group}:@{artifact}:jar:@{classifier}}"/>
+ <replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}">
+ <replacefilter token="<!-- Insert resources here -->" value="${current.maven.root} <!-- Insert resources here -->"/>
+ </replace>
+ </then>
+ <else>
+ <!-- Copy the jar to the module dir -->
+ <copy todir="${module.repo.output.dir}/${current.module.path}" failonerror="true">
+ <fileset file="${@{group}:@{artifact}:jar:@{classifier}}"/>
+ <!-- http://jira.codehaus.org/browse/MANTRUN-159 -->
+ <mapper type="flatten" />
+ </copy>
+
+ <basename file="${@{group}:@{artifact}:jar:@{classifier}}" property="resourcename.@{group}.@{artifact}.@{classifier}"/>
+
+ <!-- Update the resource entry in module.xml -->
+ <define-resource-root path="${resourcename.@{group}.@{artifact}.@{classifier}}"/>
+ <replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}">
+ <replacefilter token="<!-- Insert resources here -->" value="${current.resource.root} <!-- Insert resources here -->"/>
+ </replace>
+ </else>
+ </if>
+ </sequential>
+ </macrodef>
+
+ <macrodef name="extract-native-jar" >
+ <attribute name="group"/>
+ <attribute name="artifact"/>
+ <sequential>
+ <if>
+ <equals arg1="${mavenized.modules}" arg2="true"/>
+ <then>
+ <define-maven-artifact group="@{group}" artifact="@{artifact}" element="native-artifact" path="${@{group}:@{artifact}:jar}"/>
+ <replace file="${module.repo.output.dir}/${current.module.path}/${module.xml}">
+ <replacefilter token="<!-- Insert resources here -->" value="${current.maven.root} <!-- Insert resources here -->"/>
+ </replace>
+ </then>
+
+ <else>
+ <unzip src="${@{group}:@{artifact}:jar}" dest="${module.repo.output.dir}/${current.module.path}">
+ <patternset>
+ <include name="lib/**"/>
+ </patternset>
+ </unzip>
+ </else>
+ </if>
+ </sequential>
+ </macrodef>
+
+ <scriptdef name="define-resource-root" language="javascript" manager="bsf">
+ <attribute name="path"/>
+ <attribute name="jandex"/>
+ <![CDATA[
+ path = attributes.get("path");
+ root = "<resource-root path=\"" + path + "\"/>";
+ if(path.indexOf('${') != -1) {
+ throw "Module resource root not found, make sure it is listed in build/pom.xml" + path;
+ }
+ if(attributes.get("jandex") == "true" ) {
+ root = root + "\n\t<resource-root path=\"" + path.replace(".jar","-jandex.jar") + "\"/>";
+ }
+ project.setProperty("current.resource.root", root);
+ ]]>
+ </scriptdef>
+
+</project>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/pom.xml b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
new file mode 100755
index 0000000..d79e42c
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml
@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<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>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.3.0.Final-SNAPSHOT</version>
+ <relativePath>../../../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-wf8-modules</artifactId>
+
+ <name>Keycloak Wildfly 8 Modules</name>
+ <packaging>pom</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-jboss-adapter-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-undertow-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wildfly-adapter</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wf8-subsystem</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <inherited>false</inherited>
+ <executions>
+ <execution>
+ <id>build-dist</id>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <phase>compile</phase>
+ <configuration>
+ <target>
+ <ant antfile="build.xml" inheritRefs="true">
+ <target name="all"/>
+ </ant>
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss</groupId>
+ <artifactId>jandex</artifactId>
+ <version>1.0.3.Final</version>
+ </dependency>
+ <dependency>
+ <groupId>ant-contrib</groupId>
+ <artifactId>ant-contrib</artifactId>
+ <version>1.0b3</version>
+ <exclusions>
+ <exclusion>
+ <groupId>ant</groupId>
+ <artifactId>ant</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.ant</groupId>
+ <artifactId>ant-apache-bsf</artifactId>
+ <version>1.9.3</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.bsf</groupId>
+ <artifactId>bsf-api</artifactId>
+ <version>3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>rhino</groupId>
+ <artifactId>js</artifactId>
+ <version>1.7R2</version>
+ </dependency>
+ </dependencies>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>assemble</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <descriptors>
+ <descriptor>assembly.xml</descriptor>
+ </descriptors>
+ <outputDirectory>
+ target
+ </outputDirectory>
+ <workDirectory>
+ target/assembly/work
+ </workDirectory>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/net/iharder/base64/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/net/iharder/base64/main/module.xml
new file mode 100755
index 0000000..c99b968
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/net/iharder/base64/main/module.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="net.iharder.base64">
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+ <dependencies>
+ <module name="javax.api"/>
+ </dependencies>
+
+</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/apache/httpcomponents/4.3/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/apache/httpcomponents/4.3/module.xml
new file mode 100644
index 0000000..a3e65f8
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/apache/httpcomponents/4.3/module.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<module xmlns="urn:jboss:module:1.1" name="org.apache.httpcomponents" slot="4.3">
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+
+ <dependencies>
+ <module name="javax.api"/>
+ <module name="org.apache.commons.codec"/>
+ <module name="org.apache.commons.logging"/>
+ <module name="org.apache.james.mime4j"/>
+ </dependencies>
+</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
new file mode 100755
index 0000000..1be1486
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-adapter-core/main/module.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-adapter-core">
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+ <dependencies>
+ <module name="javax.api"/>
+ <module name="org.codehaus.jackson.jackson-core-asl"/>
+ <module name="org.codehaus.jackson.jackson-mapper-asl"/>
+ <module name="org.codehaus.jackson.jackson-xc"/>
+ <module name="org.apache.httpcomponents" slot="4.3" />
+ <module name="org.jboss.logging"/>
+ <module name="org.keycloak.keycloak-core"/>
+ <module name="net.iharder.base64"/>
+ </dependencies>
+
+</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
new file mode 100755
index 0000000..545f168
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-core">
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+ <dependencies>
+ <module name="org.codehaus.jackson.jackson-core-asl"/>
+ <module name="org.codehaus.jackson.jackson-mapper-asl"/>
+ <module name="org.codehaus.jackson.jackson-xc"/>
+ <module name="org.bouncycastle" />
+ <module name="net.iharder.base64"/>
+ <module name="javax.api"/>
+ <module name="javax.activation.api"/>
+ <module name="sun.jdk" optional="true" />
+ <module name="sun.jdk.jgss" optional="true" />
+ </dependencies>
+
+</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
new file mode 100755
index 0000000..beac07b
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-jboss-adapter-core/main/module.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-jboss-adapter-core">
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+ <dependencies>
+ <module name="javax.api"/>
+ <module name="org.jboss.logging"/>
+ <module name="org.picketbox"/>
+ <module name="org.keycloak.keycloak-adapter-core"/>
+ <module name="org.keycloak.keycloak-core"/>
+ </dependencies>
+
+</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml
new file mode 100755
index 0000000..1772a22
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-undertow-adapter/main/module.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-undertow-adapter">
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+ <dependencies>
+ <module name="javax.api"/>
+ <module name="org.bouncycastle" />
+ <module name="org.codehaus.jackson.jackson-core-asl"/>
+ <module name="org.codehaus.jackson.jackson-mapper-asl"/>
+ <module name="org.codehaus.jackson.jackson-xc"/>
+ <module name="org.apache.httpcomponents" slot="4.3" />
+ <module name="javax.servlet.api"/>
+ <module name="org.jboss.logging"/>
+ <module name="org.jboss.xnio"/>
+ <module name="io.undertow.core"/>
+ <module name="io.undertow.servlet"/>
+ <module name="org.keycloak.keycloak-adapter-core"/>
+ <module name="org.keycloak.keycloak-core"/>
+ </dependencies>
+
+</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wf8-subsystem/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wf8-subsystem/main/module.xml
new file mode 100755
index 0000000..9fdf2b6
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wf8-subsystem/main/module.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ ~ JBoss, Home of Professional Open Source.
+ ~ Copyright 2014, Red Hat, Inc., and individual contributors
+ ~ as indicated by the @author tags. See the copyright.txt file in the
+ ~ distribution for a full listing of individual contributors.
+ ~
+ ~ This is free software; you can redistribute it and/or modify it
+ ~ under the terms of the GNU Lesser General Public License as
+ ~ published by the Free Software Foundation; either version 2.1 of
+ ~ the License, or (at your option) any later version.
+ ~
+ ~ This software is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ ~ Lesser General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU Lesser General Public
+ ~ License along with this software; if not, write to the Free
+ ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ -->
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-wf8-subsystem">
+ <properties>
+ <property name="keycloak-version" value="${project.version}"/>
+ </properties>
+
+ <resources>
+ <resource-root path="."/>
+ <!-- Insert resources here -->
+ </resources>
+
+ <dependencies>
+ <module name="javax.api"/>
+ <module name="org.jboss.staxmapper"/>
+ <module name="org.jboss.as.controller"/>
+ <module name="org.jboss.as.ee"/>
+ <module name="org.jboss.as.server"/>
+ <module name="org.jboss.modules"/>
+ <module name="org.jboss.msc"/>
+ <module name="org.jboss.logging"/>
+ <module name="org.jboss.vfs"/>
+ <module name="org.jboss.as.web-common"/>
+ <module name="org.jboss.metadata"/>
+ </dependencies>
+</module>
diff --git a/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml
new file mode 100755
index 0000000..2b0e537
--- /dev/null
+++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-wildfly-adapter/main/module.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+
+
+<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-wildfly-adapter">
+ <resources>
+ <!-- Insert resources here -->
+ </resources>
+ <dependencies>
+ <module name="javax.api"/>
+ <module name="org.bouncycastle" />
+ <module name="org.codehaus.jackson.jackson-core-asl"/>
+ <module name="org.codehaus.jackson.jackson-mapper-asl"/>
+ <module name="org.codehaus.jackson.jackson-xc"/>
+ <module name="org.apache.httpcomponents" slot="4.3" />
+ <module name="javax.servlet.api"/>
+ <module name="org.jboss.logging"/>
+ <module name="io.undertow.core"/>
+ <module name="io.undertow.servlet"/>
+ <module name="org.picketbox"/>
+ <module name="org.keycloak.keycloak-undertow-adapter"/>
+ <module name="org.keycloak.keycloak-adapter-core"/>
+ <module name="org.keycloak.keycloak-core"/>
+ </dependencies>
+
+</module>
diff --git a/distribution/adapters/wildfly-adapter-zip/assembly.xml b/distribution/adapters/wildfly-adapter-zip/assembly.xml
index 9448028..738ad2a 100755
--- a/distribution/adapters/wildfly-adapter-zip/assembly.xml
+++ b/distribution/adapters/wildfly-adapter-zip/assembly.xml
@@ -18,7 +18,7 @@
<include>org/keycloak/keycloak-jboss-adapter-core/**</include>
<include>org/keycloak/keycloak-undertow-adapter/**</include>
<include>org/keycloak/keycloak-wildfly-adapter/**</include>
- <include>org/keycloak/keycloak-subsystem/**</include>
+ <include>org/keycloak/keycloak-adapter-subsystem/**</include>
</includes>
<excludes>
<exclude>**/*.war</exclude>
diff --git a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
index 8b3d40c..24a5cd8 100755
--- a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
+++ b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java
@@ -7,10 +7,10 @@ import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.keycloak.OAuth2Constants;
import org.keycloak.constants.ServiceUrlConstants;
-import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.util.HostUtils;
@@ -70,8 +70,7 @@ public class AdminClient {
public static AccessTokenResponse getToken(HttpServletRequest request) throws IOException {
- HttpClient client = new HttpClientBuilder()
- .disableTrustManager().build();
+ HttpClient client = new DefaultHttpClient();
try {
@@ -104,8 +103,7 @@ public class AdminClient {
public static void logout(HttpServletRequest request, AccessTokenResponse res) throws IOException {
- HttpClient client = new HttpClientBuilder()
- .disableTrustManager().build();
+ HttpClient client = new DefaultHttpClient();
try {
@@ -135,8 +133,7 @@ public class AdminClient {
public static List<RoleRepresentation> getRealmRoles(HttpServletRequest request, AccessTokenResponse res) throws Failure {
- HttpClient client = new HttpClientBuilder()
- .disableTrustManager().build();
+ HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(getBaseUrl(request) + "/auth/admin/realms/demo/roles");
get.addHeader("Authorization", "Bearer " + res.getToken());
diff --git a/examples/demo-template/customer-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/customer-app/src/main/java/org/keycloak/example/AdminClient.java
index 7b17e18..c111f36 100755
--- a/examples/demo-template/customer-app/src/main/java/org/keycloak/example/AdminClient.java
+++ b/examples/demo-template/customer-app/src/main/java/org/keycloak/example/AdminClient.java
@@ -4,9 +4,9 @@ import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
-import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.util.JsonSerialization;
@@ -40,8 +40,7 @@ public class AdminClient {
public static List<RoleRepresentation> getRealmRoles(HttpServletRequest req) throws Failure {
KeycloakSecurityContext session = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
- HttpClient client = new HttpClientBuilder()
- .disableTrustManager().build();
+ HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/auth/admin/realms/demo/roles");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
diff --git a/examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java b/examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java
index 3a0409b..e9dcbb1 100755
--- a/examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java
+++ b/examples/demo-template/customer-app/src/main/java/org/keycloak/example/CustomerDatabaseClient.java
@@ -4,13 +4,11 @@ import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
-import org.keycloak.adapters.HttpClientBuilder;
-import org.keycloak.constants.ServiceUrlConstants;
import org.keycloak.representations.IDToken;
import org.keycloak.util.JsonSerialization;
-import org.keycloak.util.KeycloakUriBuilder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@@ -49,8 +47,7 @@ public class CustomerDatabaseClient {
public static List<String> getCustomers(HttpServletRequest req) throws Failure {
KeycloakSecurityContext session = (KeycloakSecurityContext) req.getAttribute(KeycloakSecurityContext.class.getName());
- HttpClient client = new HttpClientBuilder()
- .disableTrustManager().build();
+ HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/database/customers");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
diff --git a/examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java b/examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
index f259d0c..3e86343 100755
--- a/examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
+++ b/examples/demo-template/product-app/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java
@@ -4,9 +4,9 @@ import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.adapters.AdapterUtils;
-import org.keycloak.adapters.HttpClientBuilder;
import org.keycloak.util.JsonSerialization;
import javax.servlet.http.HttpServletRequest;
@@ -37,8 +37,8 @@ public class ProductDatabaseClient
public static List<String> getProducts(HttpServletRequest req) throws Failure {
KeycloakSecurityContext session = (KeycloakSecurityContext)req.getAttribute(KeycloakSecurityContext.class.getName());
- HttpClient client = new HttpClientBuilder()
- .disableTrustManager().build();
+
+ HttpClient client = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(AdapterUtils.getOriginForRestCalls(req.getRequestURL().toString(), session) + "/database/products");
get.addHeader("Authorization", "Bearer " + session.getTokenString());
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index 166c5bf..c020f51 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -24,7 +24,6 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationEventAwareProviderFactory;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
-import org.keycloak.models.UserFederationProviderFactory;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
@@ -89,7 +88,7 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute();
UserFederationMapperModel mapperModel;
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("usernameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
@@ -97,25 +96,25 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
// For AD deployments with sAMAccountName is probably more common to map "cn" to full name of user
if (activeDirectory && usernameLdapAttribute.equalsIgnoreCase(LDAPConstants.SAM_ACCOUNT_NAME)) {
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("fullNameMapper", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel);
} else {
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("firstNameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel);
}
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("lastNameMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("last name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel);
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("emailMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
@@ -125,14 +124,14 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
String modifyTimestampLdapAttrName = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
// map createTimeStamp as read-only
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creationDateMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creation date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
UserAttributeLDAPFederationMapper.READ_ONLY, "true");
realm.addUserFederationMapper(mapperModel);
// map modifyTimeStamp as read-only
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modifyDateMapper", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modify date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
UserAttributeLDAPFederationMapper.READ_ONLY, "true");
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
index 6b8f186..33ace78 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/AbstractLDAPFederationMapperFactory.java
@@ -1,25 +1,65 @@
package org.keycloak.federation.ldap.mappers;
+import java.util.List;
+import java.util.Map;
+
import org.keycloak.Config;
+import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.mappers.UserFederationMapperFactory;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.provider.ProviderConfigProperty;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public abstract class AbstractLDAPFederationMapperFactory implements UserFederationMapperFactory {
+ // Used to map attributes from LDAP to UserModel attributes
+ public static final String ATTRIBUTE_MAPPER_CATEGORY = "Attribute Mapper";
+
+ // Used to map roles from LDAP to UserModel users
+ public static final String ROLE_MAPPER_CATEGORY = "Role Mapper";
+
@Override
public void init(Config.Scope config) {
}
@Override
+ public String getFederationProviderType() {
+ return LDAPFederationProviderFactory.PROVIDER_NAME;
+ }
+
+ @Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
+ public List<ProviderConfigProperty> getConfigProperties() {
+ throw new IllegalStateException("Method not supported for this implementation");
+ }
+
+ @Override
public void close() {
}
+ public static ProviderConfigProperty createConfigProperty(String name, String label, String helpText, String type, Object defaultValue) {
+ ProviderConfigProperty configProperty = new ProviderConfigProperty();
+ configProperty.setName(name);
+ configProperty.setLabel(label);
+ configProperty.setHelpText(helpText);
+ configProperty.setType(type);
+ configProperty.setDefaultValue(defaultValue);
+ return configProperty;
+ }
+
+ protected void checkMandatoryConfigAttribute(String name, String displayName, UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ String attrConfigValue = mapperModel.getConfig().get(name);
+ if (attrConfigValue == null || attrConfigValue.trim().isEmpty()) {
+ throw new MapperConfigValidationException("Missing configuration for '" + displayName + "'");
+ }
+ }
+
}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
index 6ef6979..d0d6230 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java
@@ -1,9 +1,14 @@
package org.keycloak.federation.ldap.mappers;
+import java.util.ArrayList;
import java.util.List;
+import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.provider.ProviderConfigProperty;
/**
@@ -11,21 +16,48 @@ import org.keycloak.provider.ProviderConfigProperty;
*/
public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
- public static final String ID = "full-name-ldap-mapper";
+ public static final String PROVIDER_ID = "full-name-ldap-mapper";
+
+ protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty userModelAttribute = createConfigProperty(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute",
+ "Name of LDAP attribute, which contains fullName of user. In most cases it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, LDAPConstants.CN);
+ configProperties.add(userModelAttribute);
+
+ ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.READ_ONLY, "Read Only",
+ "For Read-only is data imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, "false");
+ configProperties.add(readOnly);
+ }
@Override
public String getHelpText() {
- return "Some help text - full name mapper - TODO";
+ return "Used to map full-name of user from single attribute in LDAP (usually 'cn' attribute) to firstName and lastName attributes of UserModel in Keycloak DB";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return ATTRIBUTE_MAPPER_CATEGORY;
}
@Override
- public List<ProviderConfigProperty> getConfigProperties() {
- return null;
+ public String getDisplayType() {
+ return "Full Name";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties(RealmModel realm) {
+ return configProperties;
}
@Override
public String getId() {
- return ID;
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ checkMandatoryConfigAttribute(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", mapperModel);
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
index 47f288d..084b255 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapper.java
@@ -178,7 +178,6 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
}
String[] objClasses = objectClasses.split(",");
- // TODO: util method for trim and convert array to collection?
Set<String> trimmed = new HashSet<String>();
for (String objectClass : objClasses) {
objectClass = objectClass.trim();
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
index cbae850..a5eadd9 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java
@@ -1,9 +1,18 @@
package org.keycloak.federation.ldap.mappers;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
+import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.provider.ProviderConfigProperty;
/**
@@ -11,21 +20,95 @@ import org.keycloak.provider.ProviderConfigProperty;
*/
public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
- public static final String ID = "role-ldap-mapper";
+ public static final String PROVIDER_ID = "role-ldap-mapper";
+
+ protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty rolesDn = createConfigProperty(RoleLDAPFederationMapper.ROLES_DN, "LDAP Roles DN",
+ "LDAP DN where are roles of this tree saved. For example 'ou=finance,dc=example,dc=org' ", ProviderConfigProperty.STRING_TYPE, null);
+ configProperties.add(rolesDn);
+
+ ProviderConfigProperty roleNameLDAPAttribute = createConfigProperty(RoleLDAPFederationMapper.ROLE_NAME_LDAP_ATTRIBUTE, "Role Name LDAP Attribute",
+ "Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=role1,ou=finance,dc=example,dc=org' ",
+ ProviderConfigProperty.STRING_TYPE, LDAPConstants.CN);
+ configProperties.add(roleNameLDAPAttribute);
+
+ ProviderConfigProperty membershipLDAPAttribute = createConfigProperty(RoleLDAPFederationMapper.MEMBERSHIP_LDAP_ATTRIBUTE, "Membership LDAP Attribute",
+ "Name of LDAP attribute on role, which is used for membership mappings. Usually it will be 'member' ",
+ ProviderConfigProperty.STRING_TYPE, LDAPConstants.MEMBER);
+ configProperties.add(membershipLDAPAttribute);
+
+ ProviderConfigProperty roleObjectClasses = createConfigProperty(RoleLDAPFederationMapper.ROLE_OBJECT_CLASSES, "Role Object Classes",
+ "Object classes of the role object divided by comma (if more values needed). In typical LDAP deployment it could be 'groupOfNames' or 'groupOfEntries' ",
+ ProviderConfigProperty.STRING_TYPE, LDAPConstants.GROUP_OF_NAMES);
+ configProperties.add(roleObjectClasses);
+
+ List<String> modes = new LinkedList<String>();
+ for (RoleLDAPFederationMapper.Mode mode : RoleLDAPFederationMapper.Mode.values()) {
+ modes.add(mode.toString());
+ }
+ ProviderConfigProperty mode = createConfigProperty(RoleLDAPFederationMapper.MODE, "Mode",
+ "LDAP_ONLY means that all role mappings are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where role mappings are " +
+ "retrieved from both LDAP and DB and merged together. New role grants are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where role mappings are retrieved from LDAP just at the time when user is imported from LDAP and then " +
+ "they are saved to local keycloak DB.",
+ ProviderConfigProperty.LIST_TYPE, modes);
+ configProperties.add(mode);
+
+ ProviderConfigProperty useRealmRolesMappings = createConfigProperty(RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING, "Use Realm Roles Mapping",
+ "If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings", ProviderConfigProperty.BOOLEAN_TYPE, "true");
+ configProperties.add(useRealmRolesMappings);
+
+ // NOTE: ClientID will be computed dynamically from available clients
+ }
@Override
public String getHelpText() {
- return "Some help text - role mapper - TODO";
+ return "Used to map role mappings of roles from some LDAP DN to Keycloak role mappings of either realm roles or client roles of particular client";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return ROLE_MAPPER_CATEGORY;
}
@Override
- public List<ProviderConfigProperty> getConfigProperties() {
- return null;
+ public String getDisplayType() {
+ return "Role mappings";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties(RealmModel realm) {
+ List<ProviderConfigProperty> props = new ArrayList<ProviderConfigProperty>(configProperties);
+
+ Map<String, ClientModel> clients = realm.getClientNameMap();
+ List<String> clientIds = new ArrayList<String>(clients.keySet());
+
+ ProviderConfigProperty clientIdProperty = createConfigProperty(RoleLDAPFederationMapper.CLIENT_ID, "Client ID",
+ "Client ID of client to which LDAP role mappings will be mapped. Applicable just if 'Use Realm Roles Mapping' is false",
+ ProviderConfigProperty.LIST_TYPE, clientIds);
+ props.add(clientIdProperty);
+
+ return props;
}
@Override
public String getId() {
- return ID ;
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ checkMandatoryConfigAttribute(RoleLDAPFederationMapper.ROLES_DN, "LDAP Roles DN", mapperModel);
+
+ String realmMappings = mapperModel.getConfig().get(RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING);
+ boolean useRealmMappings = Boolean.parseBoolean(realmMappings);
+ if (!useRealmMappings) {
+ String clientId = mapperModel.getConfig().get(RoleLDAPFederationMapper.CLIENT_ID);
+ if (clientId == null || clientId.trim().isEmpty()) {
+ throw new MapperConfigValidationException("Client ID needs to be provided in config when Realm Roles Mapping is not used");
+ }
+ }
}
@Override
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
index 564b012..c0b9d79 100644
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java
@@ -1,9 +1,13 @@
package org.keycloak.federation.ldap.mappers;
+import java.util.ArrayList;
import java.util.List;
+import org.keycloak.mappers.MapperConfigValidationException;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.provider.ProviderConfigProperty;
/**
@@ -11,21 +15,52 @@ import org.keycloak.provider.ProviderConfigProperty;
*/
public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory {
- public static final String ID = "user-attribute-ldap-mapper";
+ public static final String PROVIDER_ID = "user-attribute-ldap-mapper";
+ protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
+
+ static {
+ ProviderConfigProperty userModelAttribute = createConfigProperty(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute",
+ "Name of mapped UserModel property or UserModel attribute in Keycloak DB. For example 'firstName', 'lastName, 'email', 'street' etc.", ProviderConfigProperty.STRING_TYPE, null);
+ configProperties.add(userModelAttribute);
+
+ ProviderConfigProperty ldapAttribute = createConfigProperty(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute",
+ "Name of mapped attribute on LDAP object. For example 'cn', 'sn, 'mail', 'street' etc.", ProviderConfigProperty.STRING_TYPE, null);
+ configProperties.add(ldapAttribute);
+
+ ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.READ_ONLY, "Read Only",
+ "Read-only attribute is imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, "false");
+ configProperties.add(readOnly);
+ }
@Override
public String getHelpText() {
- return "Some help text TODO";
+ return "Used to map single attribute from LDAP user to attribute of UserModel in Keycloak DB";
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return ATTRIBUTE_MAPPER_CATEGORY;
}
@Override
- public List<ProviderConfigProperty> getConfigProperties() {
- return null;
+ public String getDisplayType() {
+ return "User Attribute";
+ }
+
+ @Override
+ public List<ProviderConfigProperty> getConfigProperties(RealmModel realm) {
+ return configProperties;
}
@Override
public String getId() {
- return ID;
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException {
+ checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", mapperModel);
+ checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", mapperModel);
}
@Override
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
index 56f6008..7a0ae1a 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -952,6 +952,58 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'GenericUserFederationCtrl'
})
+ .when('/realms/:realm/user-federation/providers/:provider/:instance/mappers', {
+ templateUrl : function(params){ return resourceUrl + '/partials/federated-mappers.html'; },
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ provider : function(UserFederationInstanceLoader) {
+ return UserFederationInstanceLoader();
+ },
+ mapperTypes : function(UserFederationMapperTypesLoader) {
+ return UserFederationMapperTypesLoader();
+ },
+ mappers : function(UserFederationMappersLoader) {
+ return UserFederationMappersLoader();
+ }
+ },
+ controller : 'UserFederationMapperListCtrl'
+ })
+ .when('/realms/:realm/user-federation/providers/:provider/:instance/mappers/:mapperId', {
+ templateUrl : function(params){ return resourceUrl + '/partials/federated-mapper-detail.html'; },
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ provider : function(UserFederationInstanceLoader) {
+ return UserFederationInstanceLoader();
+ },
+ mapperTypes : function(UserFederationMapperTypesLoader) {
+ return UserFederationMapperTypesLoader();
+ },
+ mapper : function(UserFederationMapperLoader) {
+ return UserFederationMapperLoader();
+ }
+ },
+ controller : 'UserFederationMapperCtrl'
+ })
+ .when('/create/user-federation-mappers/:realm/:provider/:instance', {
+ templateUrl : function(params){ return resourceUrl + '/partials/federated-mapper-detail.html'; },
+ resolve : {
+ realm : function(RealmLoader) {
+ return RealmLoader();
+ },
+ provider : function(UserFederationInstanceLoader) {
+ return UserFederationInstanceLoader();
+ },
+ mapperTypes : function(UserFederationMapperTypesLoader) {
+ return UserFederationMapperTypesLoader();
+ },
+ },
+ controller : 'UserFederationMapperCreateCtrl'
+ })
+
.when('/realms/:realm/defense/headers', {
templateUrl : resourceUrl + '/partials/defense-headers.html',
resolve : {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index 2444be4..bc723f3 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -511,8 +511,8 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif
}
function triggerSync(action) {
- UserFederationSync.get({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, function() {
- Notifications.success("Sync of users finished successfully");
+ UserFederationSync.save({ action: action, realm: $scope.realm.realm, provider: $scope.instance.id }, {}, function(syncResult) {
+ Notifications.success("Sync of users finished successfully. " + syncResult.status);
}, function() {
Notifications.error("Error during sync of users");
});
@@ -734,3 +734,128 @@ module.controller('LDAPCtrl', function($scope, $location, $route, Notifications,
});
+
+module.controller('UserFederationMapperListCtrl', function($scope, $location, Notifications, $route, Dialog, realm, provider, mapperTypes, mappers) {
+ console.log('UserFederationMapperListCtrl');
+
+ $scope.realm = realm;
+ $scope.provider = provider;
+
+ $scope.mapperTypes = mapperTypes;
+ $scope.mappers = mappers;
+
+ $scope.hasAnyMapperTypes = false;
+ for (var property in mapperTypes) {
+ if (!(property.startsWith('$'))) {
+ $scope.hasAnyMapperTypes = true;
+ break;
+ }
+ }
+
+});
+
+module.controller('UserFederationMapperCtrl', function($scope, realm, provider, mapperTypes, mapper, UserFederationMapper, Notifications, Dialog, $location) {
+ console.log('UserFederationMapperCtrl');
+ $scope.realm = realm;
+ $scope.provider = provider;
+ $scope.create = false;
+ $scope.mapper = angular.copy(mapper);
+ $scope.changed = false;
+ $scope.mapperType = mapperTypes[mapper.federationMapperType];
+
+ $scope.$watch('mapper', function() {
+ if (!angular.equals($scope.mapper, mapper)) {
+ $scope.changed = true;
+ }
+ }, true);
+
+ $scope.save = function() {
+ UserFederationMapper.update({
+ realm : realm.realm,
+ provider: provider.id,
+ mapperId : mapper.id
+ }, $scope.mapper, function() {
+ $scope.changed = false;
+ mapper = angular.copy($scope.mapper);
+ $location.url("/realms/" + realm.realm + '/user-federation/providers/' + provider.providerName + '/' + provider.id + '/mappers/' + mapper.id);
+ Notifications.success("Your changes have been saved.");
+ }, function(error) {
+ if (error.status == 400) {
+ Notifications.error('Error in configuration of mapper: ' + error.data.error_description);
+ } else {
+ Notification.error('Unexpected error when creating mapper');
+ }
+ });
+ };
+
+ $scope.reset = function() {
+ $scope.mapper = angular.copy(mapper);
+ $scope.changed = false;
+ };
+
+ $scope.cancel = function() {
+ window.history.back();
+ };
+
+ $scope.remove = function() {
+ Dialog.confirmDelete($scope.mapper.name, 'mapper', function() {
+ UserFederationMapper.remove({ realm: realm.realm, provider: provider.id, mapperId : $scope.mapper.id }, function() {
+ Notifications.success("The mapper has been deleted.");
+ $location.url("/realms/" + realm.realm + '/user-federation/providers/' + provider.providerName + '/' + provider.id + '/mappers');
+ });
+ });
+ };
+
+});
+
+module.controller('UserFederationMapperCreateCtrl', function($scope, realm, provider, mapperTypes, UserFederationMapper, Notifications, Dialog, $location) {
+ console.log('UserFederationMapperCreateCtrl');
+ $scope.realm = realm;
+ $scope.provider = provider;
+ $scope.create = true;
+ $scope.mapper = { federationProviderDisplayName: provider.displayName, config: {}};
+ $scope.mapperTypes = mapperTypes;
+ $scope.mapperType = null;
+
+ $scope.$watch('mapperType', function() {
+ if ($scope.mapperType != null) {
+ $scope.mapper.config = {};
+ for ( var i = 0; i < $scope.mapperType.properties.length; i++) {
+ var property = $scope.mapperType.properties[i];
+ if (property.type === 'String' || property.type === 'boolean') {
+ $scope.mapper.config[ property.name ] = property.defaultValue;
+ }
+ }
+ }
+ }, true);
+
+ $scope.save = function() {
+ if ($scope.mapperType == null) {
+ Notifications.error("You need to select mapper type!");
+ return;
+ }
+
+ $scope.mapper.federationMapperType = $scope.mapperType.id;
+ UserFederationMapper.save({
+ realm : realm.realm, provider: provider.id
+ }, $scope.mapper, function(data, headers) {
+ var l = headers().location;
+ var id = l.substring(l.lastIndexOf("/") + 1);
+ $location.url('/realms/' + realm.realm +'/user-federation/providers/' + provider.providerName + '/' + provider.id + '/mappers/' + id);
+ Notifications.success("Mapper has been created.");
+ }, function(error) {
+ if (error.status == 400) {
+ Notifications.error('Error in configuration of mapper: ' + error.data.error_description);
+ } else {
+ Notification.error('Unexpected error when creating mapper');
+ }
+ });
+ };
+
+ $scope.cancel = function() {
+ window.history.back();
+ };
+
+
+});
+
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index 3f72ffe..3a492bb 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -116,6 +116,34 @@ module.factory('UserFederationFactoryLoader', function(Loader, UserFederationPro
});
});
+module.factory('UserFederationMapperTypesLoader', function(Loader, UserFederationMapperTypes, $route, $q) {
+ return Loader.get(UserFederationMapperTypes, function () {
+ return {
+ realm: $route.current.params.realm,
+ provider: $route.current.params.instance
+ }
+ });
+});
+
+module.factory('UserFederationMappersLoader', function(Loader, UserFederationMappers, $route, $q) {
+ return Loader.query(UserFederationMappers, function () {
+ return {
+ realm: $route.current.params.realm,
+ provider: $route.current.params.instance
+ }
+ });
+});
+
+module.factory('UserFederationMapperLoader', function(Loader, UserFederationMapper, $route, $q) {
+ return Loader.get(UserFederationMapper, function () {
+ return {
+ realm: $route.current.params.realm,
+ provider: $route.current.params.instance,
+ mapperId: $route.current.params.mapperId
+ }
+ });
+});
+
module.factory('UserSessionStatsLoader', function(Loader, UserSessionStats, $route, $q) {
return Loader.get(UserSessionStats, function() {
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
index e9f09a2..f192516 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js
@@ -238,7 +238,33 @@ module.factory('UserFederationProviders', function($resource) {
});
module.factory('UserFederationSync', function($resource) {
- return $resource(authUrl + '/admin/realms/:realm/user-federation/sync/:provider');
+ return $resource(authUrl + '/admin/realms/:realm/user-federation/instances/:provider/sync');
+});
+
+module.factory('UserFederationMapperTypes', function($resource) {
+ return $resource(authUrl + '/admin/realms/:realm/user-federation/instances/:provider/mapper-types', {
+ realm : '@realm',
+ provider : '@provider'
+ });
+});
+
+module.factory('UserFederationMappers', function($resource) {
+ return $resource(authUrl + '/admin/realms/:realm/user-federation/instances/:provider/mappers', {
+ realm : '@realm',
+ provider : '@provider'
+ });
+});
+
+module.factory('UserFederationMapper', function($resource) {
+ return $resource(authUrl + '/admin/realms/:realm/user-federation/instances/:provider/mappers/:mapperId', {
+ realm : '@realm',
+ provider : '@provider',
+ mapperId: '@mapperId'
+ }, {
+ update: {
+ method : 'PUT'
+ }
+ });
});
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-generic.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-generic.html
index b2c7da1..f0d8774 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-generic.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-generic.html
@@ -5,8 +5,13 @@
<li data-ng-show="create">Add User Federation Provider</li>
</ol>
- <h1 data-ng-hide="create"><strong>User Federation Provider</strong> {{instance.displayName|capitalize}}</h1>
- <h1 data-ng-show="create"><strong>Add User Federation Provider</strong></h1>
+ <h1 data-ng-hide="create"><strong>{{instance.providerName|capitalize}} User Federation Provider</strong> {{instance.displayName|capitalize}}</h1>
+ <h1 data-ng-show="create"><strong>Add {{instance.providerName|capitalize}} User Federation Provider</strong></h1>
+
+ <ul class="nav nav-tabs" data-ng-hide="create">
+ <li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>
+ <li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}/mappers">Mappers</a></li>
+ </ul>
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-kerberos.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-kerberos.html
index 2294abb..b2f4701 100644
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-kerberos.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-kerberos.html
@@ -8,6 +8,11 @@
<h1 data-ng-hide="create"><strong>Kerberos User Federation Provider</strong> {{instance.displayName|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add Kerberos User Federation Provider</strong></h1>
+ <ul class="nav nav-tabs" data-ng-hide="create">
+ <li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>
+ <li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}/mappers">Mappers</a></li>
+ </ul>
+
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>
<legend><span class="text">Required Settings</span></legend>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
index ec916a0..2b86f09 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html
@@ -8,6 +8,11 @@
<h1 data-ng-hide="create"><strong>LDAP User Federation Provider</strong> {{instance.displayName|capitalize}}</h1>
<h1 data-ng-show="create"><strong>Add LDAP User Federation Provider</strong></h1>
+ <ul class="nav nav-tabs" data-ng-hide="create">
+ <li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">Settings</a></li>
+ <li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}/mappers">Mappers</a></li>
+ </ul>
+
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
<fieldset>
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html
new file mode 100644
index 0000000..0b9c144
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html
@@ -0,0 +1,78 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/user-federation">User Federation</a></li>
+ <li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}">{{provider.displayName|capitalize}}</a></li>
+ <li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}/mappers">User Federation Mappers</a></li>
+ <li class="active" data-ng-show="create">Create User Federation Mapper</li>
+ <li class="active" data-ng-hide="create">{{mapper.name}}</li>
+ </ol>
+
+ <h1 data-ng-hide="create"><strong>User Federation Mapper</strong> {{mapper.name}}</h1>
+ <h1 data-ng-show="create"><strong>Add User Federation Mapper</strong></h1>
+
+ <form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
+ <fieldset>
+ <div class="form-group clearfix" data-ng-show="!create">
+ <label class="col-md-2 control-label" for="mapperId">ID </label>
+ <div class="col-md-6">
+ <input class="form-control" id="mapperId" type="text" ng-model="mapper.id" readonly>
+ </div>
+ </div>
+ <div class="form-group clearfix">
+ <label class="col-md-2 control-label" for="name">Name <span class="required">*</span></label>
+ <div class="col-md-6">
+ <input class="form-control" id="name" type="text" ng-model="mapper.name" data-ng-readonly="!create" required>
+ </div>
+ <kc-tooltip>Name of the mapper.</kc-tooltip>
+ </div>
+ <div class="form-group" data-ng-show="create">
+ <label class="col-md-2 control-label" for="mapperTypeCreate">Mapper Type</label>
+ <div class="col-sm-6">
+ <div>
+ <select class="form-control" id="mapperTypeCreate"
+ ng-model="mapperType"
+ ng-options="mapperType.name for (mapperKey, mapperType) in mapperTypes">
+ </select>
+ </div>
+ </div>
+ <kc-tooltip>{{mapperType.helpText}}</kc-tooltip>
+ </div>
+ <div class="form-group clearfix" data-ng-hide="create">
+ <label class="col-md-2 control-label" for="mapperType">Mapper Type</label>
+ <div class="col-md-6">
+ <input class="form-control" id="mapperType" type="text" ng-model="mapperType.name" data-ng-readonly="true">
+ </div>
+ <kc-tooltip>{{mapperType.helpText}}</kc-tooltip>
+ </div>
+ <div data-ng-repeat="option in mapperType.properties" class="form-group">
+ <label class="col-md-2 control-label">{{option.label}}</label>
+
+ <div class="col-sm-4" data-ng-hide="option.type == 'boolean' || option.type == 'List'">
+ <input class="form-control" type="text" data-ng-model="mapper.config[ option.name ]">
+ </div>
+ <div class="col-sm-4" data-ng-show="option.type == 'boolean'">
+ <input ng-model="mapper.config[ option.name ]" value="'true'" name="option.name" id="option.name" onoffswitchmodel />
+ </div>
+ <div class="col-sm-4" data-ng-show="option.type == 'List'">
+ <select ng-model="mapper.config[ option.name ]" ng-options="data for data in option.defaultValue">
+ <option value="" selected> Select one... </option>
+ </select>
+ </div>
+ <kc-tooltip>{{option.helpText}}</kc-tooltip>
+ </div>
+
+ </fieldset>
+ <div class="pull-right form-actions" data-ng-show="create && access.manageRealm">
+ <button kc-cancel data-ng-click="cancel()">Cancel</button>
+ <button kc-save>Save</button>
+ </div>
+
+ <div class="pull-right form-actions" data-ng-show="!create && access.manageRealm">
+ <button kc-reset data-ng-show="changed">Clear changes</button>
+ <button kc-save data-ng-show="changed">Save</button>
+ <button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
+ </div>
+ </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mappers.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mappers.html
new file mode 100644
index 0000000..d650100
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mappers.html
@@ -0,0 +1,53 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <ol class="breadcrumb">
+ <li><a href="#/realms/{{realm.realm}}/user-federation">User Federation</a></li>
+ <li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}">{{provider.displayName|capitalize}}</a></li>
+ <li>User Federation Mappers</li>
+ </ol>
+
+ <h1><strong>{{provider.providerName === 'ldap' ? 'LDAP' : (provider.providerName|capitalize)}} User Federation Provider</strong> {{provider.displayName|capitalize}}</h1>
+
+ <ul class="nav nav-tabs" data-ng-hide="create">
+ <li><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}">Settings</a></li>
+ <li class="active"><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}/mappers">Mappers</a></li>
+ </ul>
+
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th class="kc-table-actions" colspan="4">
+ <div class="form-inline">
+ <div class="form-group">
+ <div class="input-group">
+ <input type="text" placeholder="Search..." data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
+ <div class="input-group-addon">
+ <i class="fa fa-search" type="submit"></i>
+ </div>
+ </div>
+ </div>
+ <div class="pull-right" data-ng-show="hasAnyMapperTypes">
+ <a class="btn btn-primary" href="#/create/user-federation-mappers/{{realm.realm}}/{{provider.providerName}}/{{provider.id}}">Create</a>
+ </div>
+ </div>
+ </th>
+ </tr>
+ <tr data-ng-hide="mappers.length == 0">
+ <th>Name</th>
+ <th>Category</th>
+ <th>Type</th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="mapper in mappers | filter:search">
+ <td><a href="#/realms/{{realm.realm}}/user-federation/providers/{{provider.providerName}}/{{provider.id}}/mappers/{{mapper.id}}">{{mapper.name}}</a></td>
+ <td>{{mapperTypes[mapper.federationMapperType].category}}</td>
+ <td>{{mapperTypes[mapper.federationMapperType].name}}</td>
+ </tr>
+ <tr data-ng-show="mappers.length == 0">
+ <td>No mappers available</td>
+ </tr>
+ </tbody>
+ </table>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
integration/wildfly/pom.xml 1(+1 -0)
diff --git a/integration/wildfly/pom.xml b/integration/wildfly/pom.xml
index 16a4d03..3e370c6 100644
--- a/integration/wildfly/pom.xml
+++ b/integration/wildfly/pom.xml
@@ -18,5 +18,6 @@
<module>wildfly-extensions</module>
<module>wildfly-server-subsystem</module>
<module>wildfly-adapter-subsystem</module>
+ <module>wf8-subsystem</module>
</modules>
</project>
\ No newline at end of file
integration/wildfly/wf8-subsystem/pom.xml 115(+115 -0)
diff --git a/integration/wildfly/wf8-subsystem/pom.xml b/integration/wildfly/wf8-subsystem/pom.xml
new file mode 100755
index 0000000..1d72912
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/pom.xml
@@ -0,0 +1,115 @@
+<?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.3.0.Final-SNAPSHOT</version>
+ <relativePath>../../../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>keycloak-wf8-subsystem</artifactId>
+ <name>Keycloak Adapter Subsystem</name>
+ <description/>
+ <packaging>jar</packaging>
+
+ <properties>
+ <wildfly.version>8.2.0.Final</wildfly.version>
+ <wildfly.core.version>8.2.0.Final</wildfly.core.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <redirectTestOutputToFile>false</redirectTestOutputToFile>
+ <enableAssertions>true</enableAssertions>
+ <systemProperties>
+ <property>
+ <name>jboss.home</name>
+ <value>${jboss.home}</value>
+ </property>
+ </systemProperties>
+ <includes>
+ <include>**/*TestCase.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-controller</artifactId>
+ <version>${wildfly.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-server</artifactId>
+ <version>${wildfly.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-web-common</artifactId>
+ <version>${wildfly.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-annotations</artifactId>
+ <version>${jboss-logging-tools.version}</version>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+ projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jboss.logging</groupId>
+ <artifactId>jboss-logging-processor</artifactId>
+ <version>${jboss-logging-tools.version}</version>
+ <!-- This is a compile-time dependency of this project, but is not needed at compile or runtime by other
+ projects that depend on this project.-->
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-subsystem-test-framework</artifactId>
+ <version>${wildfly.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wildfly-adapter</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java
new file mode 100755
index 0000000..56b8ef1
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialAddHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.ServiceVerificationHandler;
+import org.jboss.dmr.ModelNode;
+import org.jboss.msc.service.ServiceController;
+
+import java.util.List;
+
+/**
+ * Add a credential to a deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class CredentialAddHandler extends AbstractAddStepHandler {
+
+ public CredentialAddHandler(AttributeDefinition... attributes) {
+ super(attributes);
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addCredential(operation, context.resolveExpressions(model));
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java
new file mode 100755
index 0000000..6083e93
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialDefinition.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelType;
+
+/**
+ * Defines attributes and operations for a credential.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class CredentialDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "credential";
+
+ protected static final AttributeDefinition VALUE =
+ new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false)
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true))
+ .build();
+
+ public CredentialDefinition() {
+ super(PathElement.pathElement(TAG_NAME),
+ KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
+ new CredentialAddHandler(VALUE),
+ CredentialRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+ resourceRegistration.registerReadWriteAttribute(VALUE, null, new CredentialReadWriteAttributeHandler());
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java
new file mode 100644
index 0000000..510f3ed
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialReadWriteAttributeHandler.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Update a credential value.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class CredentialReadWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.updateCredential(operation, attributeName, resolvedValue);
+
+ hh.setHandback(ckService);
+
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateCredential(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java
new file mode 100644
index 0000000..f3f7818
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/CredentialRemoveHandler.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a credential from a deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public final class CredentialRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static CredentialRemoveHandler INSTANCE = new CredentialRemoveHandler();
+
+ private CredentialRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeCredential(operation);
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java
new file mode 100755
index 0000000..4399291
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigDeploymentProcessor.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.web.common.WarMetaData;
+import org.jboss.logging.Logger;
+import org.jboss.metadata.javaee.spec.ParamValueMetaData;
+import org.jboss.metadata.web.jboss.JBossWebMetaData;
+import org.jboss.metadata.web.spec.LoginConfigMetaData;
+import org.keycloak.subsystem.wf8.logging.KeycloakLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitProcessor {
+ protected Logger log = Logger.getLogger(KeycloakAdapterConfigDeploymentProcessor.class);
+
+ // This param name is defined again in Keycloak Undertow Integration class
+ // org.keycloak.adapters.undertow.KeycloakServletExtension. We have this value in
+ // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration.
+ public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig";
+
+ // not sure if we need this yet, keeping here just in case
+ protected void addSecurityDomain(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService service) {
+ String deploymentName = deploymentUnit.getName();
+ if (!service.isSecureDeployment(deploymentName)) {
+ return;
+ }
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) return;
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) return;
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+ if (loginConfig == null || !loginConfig.getAuthMethod().equalsIgnoreCase("KEYCLOAK")) {
+ return;
+ }
+
+ webMetaData.setSecurityDomain("keycloak");
+ }
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ String deploymentName = deploymentUnit.getName();
+ KeycloakAdapterConfigService service = KeycloakAdapterConfigService.getInstance();
+ if (service.isSecureDeployment(deploymentName)) {
+ addKeycloakAuthData(phaseContext, deploymentName, service);
+ }
+
+ // FYI, Undertow Extension will find deployments that have auth-method set to KEYCLOAK
+
+ // todo notsure if we need this
+ // addSecurityDomain(deploymentUnit, service);
+ }
+
+ private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, String deploymentName, KeycloakAdapterConfigService service) throws DeploymentUnitProcessingException {
+ DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+ WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
+ if (warMetaData == null) {
+ throw new DeploymentUnitProcessingException("WarMetaData not found for " + deploymentName + ". Make sure you have specified a WAR as your secure-deployment in the Keycloak subsystem.");
+ }
+
+ addJSONData(service.getJSON(deploymentName), warMetaData);
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ LoginConfigMetaData loginConfig = webMetaData.getLoginConfig();
+ if (loginConfig == null) {
+ loginConfig = new LoginConfigMetaData();
+ webMetaData.setLoginConfig(loginConfig);
+ }
+ loginConfig.setAuthMethod("KEYCLOAK");
+ loginConfig.setRealmName(service.getRealmName(deploymentName));
+ KeycloakLogger.ROOT_LOGGER.deploymentSecured(deploymentName);
+ }
+
+ private void addJSONData(String json, WarMetaData warMetaData) {
+ JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData();
+ if (webMetaData == null) {
+ webMetaData = new JBossWebMetaData();
+ warMetaData.setMergedJBossWebMetaData(webMetaData);
+ }
+
+ List<ParamValueMetaData> contextParams = webMetaData.getContextParams();
+ if (contextParams == null) {
+ contextParams = new ArrayList<ParamValueMetaData>();
+ }
+
+ ParamValueMetaData param = new ParamValueMetaData();
+ param.setParamName(AUTH_DATA_PARAM_NAME);
+ param.setParamValue(json);
+ contextParams.add(param);
+
+ webMetaData.setContextParams(contextParams);
+ }
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
new file mode 100755
index 0000000..4843534
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.Property;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
+
+/**
+ * This service keeps track of the entire Keycloak management model so as to provide
+ * adapter configuration to each deployment at deploy time.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class KeycloakAdapterConfigService {
+
+ private static final String CREDENTIALS_JSON_NAME = "credentials";
+
+ private static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService();
+
+ public static KeycloakAdapterConfigService getInstance() {
+ return INSTANCE;
+ }
+
+ private final Map<String, ModelNode> realms = new HashMap<String, ModelNode>();
+
+ // keycloak-secured deployments
+ private final Map<String, ModelNode> secureDeployments = new HashMap<String, ModelNode>();
+
+
+ private KeycloakAdapterConfigService() {
+ }
+
+ public void addRealm(ModelNode operation, ModelNode model) {
+ this.realms.put(realmNameFromOp(operation), model.clone());
+ }
+
+ public void updateRealm(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode realm = this.realms.get(realmNameFromOp(operation));
+ realm.get(attrName).set(resolvedValue);
+ }
+
+ public void removeRealm(ModelNode operation) {
+ this.realms.remove(realmNameFromOp(operation));
+ }
+
+ public void addSecureDeployment(ModelNode operation, ModelNode model) {
+ ModelNode deployment = model.clone();
+ this.secureDeployments.put(deploymentNameFromOp(operation), deployment);
+ }
+
+ public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ deployment.get(attrName).set(resolvedValue);
+ }
+
+ public void removeSecureDeployment(ModelNode operation) {
+ this.secureDeployments.remove(deploymentNameFromOp(operation));
+ }
+
+ public void addCredential(ModelNode operation, ModelNode model) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ credentials = new ModelNode();
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ credentials.get(credentialName).set(model.get("value").asString());
+
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ deployment.get(CREDENTIALS_JSON_NAME).set(credentials);
+ }
+
+ public void removeCredential(ModelNode operation) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ throw new RuntimeException("Can not remove credential. No credential defined for deployment in op " + operation.toString());
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ credentials.remove(credentialName);
+ }
+
+ public void updateCredential(ModelNode operation, String attrName, ModelNode resolvedValue) {
+ ModelNode credentials = credentialsFromOp(operation);
+ if (!credentials.isDefined()) {
+ throw new RuntimeException("Can not update credential. No credential defined for deployment in op " + operation.toString());
+ }
+
+ String credentialName = credentialNameFromOp(operation);
+ credentials.get(credentialName).set(resolvedValue);
+ }
+
+ private ModelNode credentialsFromOp(ModelNode operation) {
+ ModelNode deployment = this.secureDeployments.get(deploymentNameFromOp(operation));
+ return deployment.get(CREDENTIALS_JSON_NAME);
+ }
+
+ private String realmNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(RealmDefinition.TAG_NAME, operation);
+ }
+
+ private String deploymentNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(SecureDeploymentDefinition.TAG_NAME, operation);
+ }
+
+ private String credentialNameFromOp(ModelNode operation) {
+ return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation);
+ }
+
+ private String valueFromOpAddress(String addrElement, ModelNode operation) {
+ String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement);
+ if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString());
+ return deploymentName;
+ }
+
+ private String getValueOfAddrElement(ModelNode address, String elementName) {
+ for (ModelNode element : address.asList()) {
+ if (element.has(elementName)) return element.get(elementName).asString();
+ }
+
+ return null;
+ }
+
+ public String getRealmName(String deploymentName) {
+ ModelNode deployment = this.secureDeployments.get(deploymentName);
+ return deployment.get(RealmDefinition.TAG_NAME).asString();
+
+ }
+
+ public String getJSON(String deploymentName) {
+ ModelNode deployment = this.secureDeployments.get(deploymentName);
+ String realmName = deployment.get(RealmDefinition.TAG_NAME).asString();
+ ModelNode realm = this.realms.get(realmName);
+
+ ModelNode json = new ModelNode();
+ json.get(RealmDefinition.TAG_NAME).set(realmName);
+
+ // Realm values set first. Some can be overridden by deployment values.
+ if (realm != null) setJSONValues(json, realm);
+ setJSONValues(json, deployment);
+ return json.toJSONString(true);
+ }
+
+ private void setJSONValues(ModelNode json, ModelNode values) {
+ for (Property prop : values.asPropertyList()) {
+ String name = prop.getName();
+ ModelNode value = prop.getValue();
+ if (value.isDefined()) {
+ json.get(name).set(value);
+ }
+ }
+ }
+
+ public boolean isSecureDeployment(String deploymentName) {
+ //log.info("********* CHECK KEYCLOAK DEPLOYMENT: deployments.size()" + deployments.size());
+
+ return this.secureDeployments.containsKey(deploymentName);
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java
new file mode 100755
index 0000000..894f662
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessor.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.server.deployment.Attachments;
+import org.jboss.as.server.deployment.DeploymentPhaseContext;
+import org.jboss.as.server.deployment.DeploymentUnit;
+import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.Module;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public abstract class KeycloakDependencyProcessor implements DeploymentUnitProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_JBOSS_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-jboss-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-adapter-core");
+ private static final ModuleIdentifier KEYCLOAK_CORE = ModuleIdentifier.create("org.keycloak.keycloak-core");
+
+ @Override
+ public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
+ final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
+
+ // Next phase, need to detect if this is a Keycloak deployment. If not, don't add the modules.
+
+ final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
+ final ModuleLoader moduleLoader = Module.getBootModuleLoader();
+ addCommonModules(moduleSpecification, moduleLoader);
+ addPlatformSpecificModules(moduleSpecification, moduleLoader);
+ }
+
+ private void addCommonModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JBOSS_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
+ }
+
+ abstract protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader);
+
+ @Override
+ public void undeploy(DeploymentUnit du) {
+
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java
new file mode 100755
index 0000000..7008fb6
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakDependencyProcessorWildFly.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.server.deployment.module.ModuleDependency;
+import org.jboss.as.server.deployment.module.ModuleSpecification;
+import org.jboss.modules.ModuleIdentifier;
+import org.jboss.modules.ModuleLoader;
+
+/**
+ * Add platform-specific modules for WildFly.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc.
+ */
+public class KeycloakDependencyProcessorWildFly extends KeycloakDependencyProcessor {
+
+ private static final ModuleIdentifier KEYCLOAK_WILDFLY_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-wildfly-adapter");
+ private static final ModuleIdentifier KEYCLOAK_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-undertow-adapter");
+
+ @Override
+ protected void addPlatformSpecificModules(ModuleSpecification moduleSpecification, ModuleLoader moduleLoader) {
+ // ModuleDependency(ModuleLoader moduleLoader, ModuleIdentifier identifier, boolean optional, boolean export, boolean importServices, boolean userSpecified)
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_WILDFLY_ADAPTER, false, false, true, false));
+ moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, false, false));
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java
new file mode 100755
index 0000000..6049f10
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakExtension.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.Extension;
+import org.jboss.as.controller.ExtensionContext;
+import org.jboss.as.controller.ModelVersion;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.ResourceDefinition;
+import org.jboss.as.controller.SubsystemRegistration;
+import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver;
+import org.jboss.as.controller.parsing.ExtensionParsingContext;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.keycloak.subsystem.wf8.logging.KeycloakLogger;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM;
+
+
+/**
+ * Main Extension class for the subsystem.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakExtension implements Extension {
+
+ public static final String SUBSYSTEM_NAME = "keycloak";
+ public static final String NAMESPACE = "urn:jboss:domain:keycloak:1.1";
+ private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser();
+ static final PathElement PATH_SUBSYSTEM = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
+ private static final String RESOURCE_NAME = KeycloakExtension.class.getPackage().getName() + ".LocalDescriptions";
+ private static final int MANAGEMENT_API_MAJOR_VERSION = 1;
+ private static final int MANAGEMENT_API_MINOR_VERSION = 0;
+ private static final int MANAGEMENT_API_MICRO_VERSION = 0;
+ static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME);
+ private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition();
+ static final RealmDefinition REALM_DEFINITION = new RealmDefinition();
+ static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition();
+ static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition();
+
+ public static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) {
+ StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME);
+ for (String kp : keyPrefix) {
+ prefix.append('.').append(kp);
+ }
+ return new StandardResourceDescriptionResolver(prefix.toString(), RESOURCE_NAME, KeycloakExtension.class.getClassLoader(), true, false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initializeParsers(final ExtensionParsingContext context) {
+ context.setSubsystemXmlMapping(SUBSYSTEM_NAME, KeycloakExtension.NAMESPACE, PARSER);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void initialize(final ExtensionContext context) {
+ KeycloakLogger.ROOT_LOGGER.debug("Activating Keycloak Extension");
+ final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MANAGEMENT_API_MAJOR_VERSION, MANAGEMENT_API_MINOR_VERSION, MANAGEMENT_API_MICRO_VERSION);
+
+ ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE);
+ registration.registerSubModel(REALM_DEFINITION);
+ ManagementResourceRegistration secureDeploymentRegistration = registration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION);
+ secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION);
+
+ subsystem.registerXMLElementWriter(PARSER);
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java
new file mode 100755
index 0000000..3be483e
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemAdd.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+
+import org.jboss.as.controller.AbstractBoottimeAddStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.ServiceVerificationHandler;
+import org.jboss.as.server.AbstractDeploymentChainStep;
+import org.jboss.as.server.DeploymentProcessorTarget;
+import org.jboss.as.server.deployment.Phase;
+import org.jboss.dmr.ModelNode;
+
+import org.jboss.as.server.deployment.DeploymentUnitProcessor;
+import org.jboss.msc.service.ServiceController;
+
+import java.util.List;
+
+/**
+ * The Keycloak subsystem add update handler.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
+
+ static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd();
+
+ @Override
+ protected void performBoottime(final OperationContext context, ModelNode operation, final ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) {
+ context.addStep(new AbstractDeploymentChainStep() {
+ @Override
+ protected void execute(DeploymentProcessorTarget processorTarget) {
+ processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME, Phase.DEPENDENCIES, 0, chooseDependencyProcessor());
+ processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME,
+ Phase.POST_MODULE, // PHASE
+ Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
+ chooseConfigDeploymentProcessor());
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+
+ private DeploymentUnitProcessor chooseDependencyProcessor() {
+ return new KeycloakDependencyProcessorWildFly();
+ }
+
+ private DeploymentUnitProcessor chooseConfigDeploymentProcessor() {
+ return new KeycloakAdapterConfigDeploymentProcessor();
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java
new file mode 100644
index 0000000..a6093cf
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemDefinition.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+
+/**
+ * Definition of subsystem=keycloak.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class KeycloakSubsystemDefinition extends SimpleResourceDefinition {
+ protected KeycloakSubsystemDefinition() {
+ super(KeycloakExtension.SUBSYSTEM_PATH,
+ KeycloakExtension.getResourceDescriptionResolver("subsystem"),
+ KeycloakSubsystemAdd.INSTANCE,
+ ReloadRequiredRemoveStepHandler.INSTANCE
+ );
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java
new file mode 100755
index 0000000..efa260b
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakSubsystemParser.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathAddress;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
+import org.jboss.as.controller.operations.common.Util;
+import org.jboss.as.controller.parsing.ParseUtils;
+import org.jboss.as.controller.persistence.SubsystemMarshallingContext;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.Property;
+import org.jboss.staxmapper.XMLElementReader;
+import org.jboss.staxmapper.XMLElementWriter;
+import org.jboss.staxmapper.XMLExtendedStreamReader;
+import org.jboss.staxmapper.XMLExtendedStreamWriter;
+
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * The subsystem parser, which uses stax to read and write to and from xml
+ */
+class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader<List<ModelNode>>, XMLElementWriter<SubsystemMarshallingContext> {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void readElement(final XMLExtendedStreamReader reader, final List<ModelNode> list) throws XMLStreamException {
+ // Require no attributes
+ ParseUtils.requireNoAttributes(reader);
+ ModelNode addKeycloakSub = Util.createAddOperation(PathAddress.pathAddress(KeycloakExtension.PATH_SUBSYSTEM));
+ list.add(addKeycloakSub);
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ if (reader.getLocalName().equals(RealmDefinition.TAG_NAME)) {
+ readRealm(reader, list);
+ }
+ else if (reader.getLocalName().equals(SecureDeploymentDefinition.TAG_NAME)) {
+ readDeployment(reader, list);
+ }
+ }
+ }
+
+ // used for debugging
+ private int nextTag(XMLExtendedStreamReader reader) throws XMLStreamException {
+ return reader.nextTag();
+ }
+
+ private void readRealm(XMLExtendedStreamReader reader, List<ModelNode> list) throws XMLStreamException {
+ String realmName = readNameAttribute(reader);
+ ModelNode addRealm = new ModelNode();
+ addRealm.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
+ PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
+ PathElement.pathElement(RealmDefinition.TAG_NAME, realmName));
+ addRealm.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ SimpleAttributeDefinition def = RealmDefinition.lookup(tagName);
+ if (def == null) throw new XMLStreamException("Unknown realm tag " + tagName);
+ def.parseAndSetParameter(reader.getElementText(), addRealm, reader);
+ }
+
+ if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addRealm)) {
+ //TODO: externalize the message
+ throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
+ }
+
+ list.add(addRealm);
+ }
+
+ private void readDeployment(XMLExtendedStreamReader reader, List<ModelNode> resourcesToAdd) throws XMLStreamException {
+ String name = readNameAttribute(reader);
+ ModelNode addSecureDeployment = new ModelNode();
+ addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
+ PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME),
+ PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name));
+ addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+ List<ModelNode> credentialsToAdd = new ArrayList<ModelNode>();
+ while (reader.hasNext() && nextTag(reader) != END_ELEMENT) {
+ String tagName = reader.getLocalName();
+ if (tagName.equals(CredentialDefinition.TAG_NAME)) {
+ readCredential(reader, addr, credentialsToAdd);
+ continue;
+ }
+
+ SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName);
+ if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName);
+ def.parseAndSetParameter(reader.getElementText(), addSecureDeployment, reader);
+ }
+
+
+ /**
+ * TODO need to check realm-ref first.
+ if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(addSecureDeployment)) {
+ //TODO: externalize the message
+ throw new XMLStreamException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
+ }
+ */
+
+ // Must add credentials after the deployment is added.
+ resourcesToAdd.add(addSecureDeployment);
+ resourcesToAdd.addAll(credentialsToAdd);
+ }
+
+ public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List<ModelNode> credentialsToAdd) throws XMLStreamException {
+ String name = readNameAttribute(reader);
+ ModelNode addCredential = new ModelNode();
+ addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD);
+ PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name));
+ addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode());
+ addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText());
+ credentialsToAdd.add(addCredential);
+ }
+
+ // expects that the current tag will have one single attribute called "name"
+ private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException {
+ String name = null;
+ for (int i = 0; i < reader.getAttributeCount(); i++) {
+ String attr = reader.getAttributeLocalName(i);
+ if (attr.equals("name")) {
+ name = reader.getAttributeValue(i);
+ continue;
+ }
+ throw ParseUtils.unexpectedAttribute(reader, i);
+ }
+ if (name == null) {
+ throw ParseUtils.missingRequired(reader, Collections.singleton("name"));
+ }
+ return name;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException {
+ context.startSubsystemElement(KeycloakExtension.NAMESPACE, false);
+ writeRealms(writer, context);
+ writeSecureDeployments(writer, context);
+ writer.writeEndElement();
+ }
+
+ private void writeRealms(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
+ if (!context.getModelNode().get(RealmDefinition.TAG_NAME).isDefined()) {
+ return;
+ }
+ for (Property realm : context.getModelNode().get(RealmDefinition.TAG_NAME).asPropertyList()) {
+ writer.writeStartElement(RealmDefinition.TAG_NAME);
+ writer.writeAttribute("name", realm.getName());
+ ModelNode realmElements = realm.getValue();
+ for (AttributeDefinition element : RealmDefinition.ALL_ATTRIBUTES) {
+ element.marshallAsElement(realmElements, writer);
+ }
+
+ writer.writeEndElement();
+ }
+ }
+
+ private void writeSecureDeployments(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
+ if (!context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).isDefined()) {
+ return;
+ }
+ for (Property deployment : context.getModelNode().get(SecureDeploymentDefinition.TAG_NAME).asPropertyList()) {
+ writer.writeStartElement(SecureDeploymentDefinition.TAG_NAME);
+ writer.writeAttribute("name", deployment.getName());
+ ModelNode deploymentElements = deployment.getValue();
+ for (AttributeDefinition element : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
+ element.marshallAsElement(deploymentElements, writer);
+ }
+
+ ModelNode credentials = deploymentElements.get(CredentialDefinition.TAG_NAME);
+ if (credentials.isDefined()) {
+ writeCredentials(writer, credentials);
+ }
+
+ writer.writeEndElement();
+ }
+ }
+
+ private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException {
+ for (Property credential : credentials.asPropertyList()) {
+ writer.writeStartElement(CredentialDefinition.TAG_NAME);
+ writer.writeAttribute("name", credential.getName());
+ String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString();
+ writeCharacters(writer, credentialValue);
+ writer.writeEndElement();
+ }
+ }
+
+ // code taken from org.jboss.as.controller.AttributeMarshaller
+ private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException {
+ if (content.indexOf('\n') > -1) {
+ // Multiline content. Use the overloaded variant that staxmapper will format
+ writer.writeCharacters(content);
+ } else {
+ // Staxmapper will just output the chars without adding newlines if this is used
+ char[] chars = content.toCharArray();
+ writer.writeCharacters(chars, 0, chars.length);
+ }
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java
new file mode 100755
index 0000000..fef809e
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmAddHandler.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.ServiceVerificationHandler;
+import org.jboss.dmr.ModelNode;
+import org.jboss.msc.service.ServiceController;
+
+import java.util.List;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+
+/**
+ * Add a new realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class RealmAddHandler extends AbstractAddStepHandler {
+
+ public static RealmAddHandler INSTANCE = new RealmAddHandler();
+
+ private RealmAddHandler() {}
+
+ @Override
+ protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
+ // TODO: localize exception. get id number
+ if (!operation.get(OP).asString().equals(ADD)) {
+ throw new OperationFailedException("Unexpected operation for add realm. operation=" + operation.toString());
+ }
+
+ for (AttributeDefinition attrib : RealmDefinition.ALL_ATTRIBUTES) {
+ attrib.validateAndSet(operation, model);
+ }
+
+ if (!SharedAttributeDefinitons.validateTruststoreSetIfRequired(model.clone())) {
+ //TODO: externalize message
+ throw new OperationFailedException("truststore and truststore-password must be set if ssl-required is not none and disable-trust-maanger is false.");
+ }
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addRealm(operation, context.resolveExpressions(model));
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java
new file mode 100755
index 0000000..628a5d7
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmDefinition.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Defines attributes and operations for the Realm
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "realm";
+
+
+ protected static final List<SimpleAttributeDefinition> REALM_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ }
+
+ protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ALL_ATTRIBUTES.addAll(REALM_ONLY_ATTRIBUTES);
+ ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
+ }
+
+ private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
+ static {
+ for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
+ DEFINITION_LOOKUP.put(def.getXmlName(), def);
+ }
+ }
+
+ private static final RealmWriteAttributeHandler realmAttrHandler = new RealmWriteAttributeHandler(ALL_ATTRIBUTES.toArray(new SimpleAttributeDefinition[0]));
+
+ public RealmDefinition() {
+ super(PathElement.pathElement("realm"),
+ KeycloakExtension.getResourceDescriptionResolver("realm"),
+ RealmAddHandler.INSTANCE,
+ RealmRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+
+ for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
+ //TODO: use subclass of realmAttrHandler that can call RealmDefinition.validateTruststoreSetIfRequired
+ resourceRegistration.registerReadWriteAttribute(attrDef, null, realmAttrHandler);
+ }
+ }
+
+
+ public static SimpleAttributeDefinition lookup(String name) {
+ return DEFINITION_LOOKUP.get(name);
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java
new file mode 100644
index 0000000..84d8ab0
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmRemoveHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class RealmRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static RealmRemoveHandler INSTANCE = new RealmRemoveHandler();
+
+ private RealmRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeRealm(operation);
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java
new file mode 100755
index 0000000..b1062c6
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/RealmWriteAttributeHandler.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Update an attribute on a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ public RealmWriteAttributeHandler(AttributeDefinition... definitions) {
+ super(definitions);
+ }
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.updateRealm(operation, attributeName, resolvedValue);
+
+ hh.setHandback(ckService);
+
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateRealm(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java
new file mode 100755
index 0000000..66cb8a7
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentAddHandler.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractAddStepHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.ServiceVerificationHandler;
+import org.jboss.dmr.ModelNode;
+import org.jboss.msc.service.ServiceController;
+
+import java.util.List;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+
+/**
+ * Add a deployment to a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class SecureDeploymentAddHandler extends AbstractAddStepHandler {
+
+ public static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler();
+
+ private SecureDeploymentAddHandler() {}
+
+ @Override
+ protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException {
+ // TODO: localize exception. get id number
+ if (!operation.get(OP).asString().equals(ADD)) {
+ throw new OperationFailedException("Unexpected operation for add secure deployment. operation=" + operation.toString());
+ }
+
+ for (AttributeDefinition attr : SecureDeploymentDefinition.ALL_ATTRIBUTES) {
+ attr.validateAndSet(operation, model);
+ }
+ }
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List<ServiceController<?>> newControllers) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.addSecureDeployment(operation, context.resolveExpressions(model));
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java
new file mode 100755
index 0000000..d16a25e
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentDefinition.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.PathElement;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleResourceDefinition;
+import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.as.controller.registry.ManagementResourceRegistration;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Defines attributes and operations for a secure-deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SecureDeploymentDefinition extends SimpleResourceDefinition {
+
+ public static final String TAG_NAME = "secure-deployment";
+
+ protected static final SimpleAttributeDefinition REALM =
+ new SimpleAttributeDefinitionBuilder("realm", ModelType.STRING, true)
+ .setXmlName("realm")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition RESOURCE =
+ new SimpleAttributeDefinitionBuilder("resource", ModelType.STRING, true)
+ .setXmlName("resource")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition USE_RESOURCE_ROLE_MAPPINGS =
+ new SimpleAttributeDefinitionBuilder("use-resource-role-mappings", ModelType.BOOLEAN, true)
+ .setXmlName("use-resource-role-mappings")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition BEARER_ONLY =
+ new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true)
+ .setXmlName("bearer-only")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition ENABLE_BASIC_AUTH =
+ new SimpleAttributeDefinitionBuilder("enable-basic-auth", ModelType.BOOLEAN, true)
+ .setXmlName("enable-basic-auth")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition PUBLIC_CLIENT =
+ new SimpleAttributeDefinitionBuilder("public-client", ModelType.BOOLEAN, true)
+ .setXmlName("public-client")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+
+ protected static final List<SimpleAttributeDefinition> DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(REALM);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(ENABLE_BASIC_AUTH);
+ DEPLOYMENT_ONLY_ATTRIBUTES.add(PUBLIC_CLIENT);
+ }
+
+ protected static final List<SimpleAttributeDefinition> ALL_ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES);
+ ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES);
+ }
+
+ private static final Map<String, SimpleAttributeDefinition> DEFINITION_LOOKUP = new HashMap<String, SimpleAttributeDefinition>();
+ static {
+ for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) {
+ DEFINITION_LOOKUP.put(def.getXmlName(), def);
+ }
+ }
+
+ private static SecureDeploymentWriteAttributeHandler attrHandler = new SecureDeploymentWriteAttributeHandler(ALL_ATTRIBUTES);
+
+ public SecureDeploymentDefinition() {
+ super(PathElement.pathElement(TAG_NAME),
+ KeycloakExtension.getResourceDescriptionResolver(TAG_NAME),
+ SecureDeploymentAddHandler.INSTANCE,
+ SecureDeploymentRemoveHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerOperations(ManagementResourceRegistration resourceRegistration) {
+ super.registerOperations(resourceRegistration);
+ resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE);
+ }
+
+ @Override
+ public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
+ super.registerAttributes(resourceRegistration);
+ for (AttributeDefinition attrDef : ALL_ATTRIBUTES) {
+ resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler);
+ }
+ }
+
+ public static SimpleAttributeDefinition lookup(String name) {
+ return DEFINITION_LOOKUP.get(name);
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java
new file mode 100644
index 0000000..6629d08
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentRemoveHandler.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractRemoveStepHandler;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.dmr.ModelNode;
+
+/**
+ * Remove a secure-deployment from a realm.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public final class SecureDeploymentRemoveHandler extends AbstractRemoveStepHandler {
+
+ public static SecureDeploymentRemoveHandler INSTANCE = new SecureDeploymentRemoveHandler();
+
+ private SecureDeploymentRemoveHandler() {}
+
+ @Override
+ protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ ckService.removeSecureDeployment(operation);
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java
new file mode 100755
index 0000000..3788c1b
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SecureDeploymentWriteAttributeHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.AbstractWriteAttributeHandler;
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.dmr.ModelNode;
+
+import java.util.List;
+
+/**
+ * Update an attribute on a secure-deployment.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SecureDeploymentWriteAttributeHandler extends AbstractWriteAttributeHandler<KeycloakAdapterConfigService> {
+
+ public SecureDeploymentWriteAttributeHandler(List<SimpleAttributeDefinition> definitions) {
+ this(definitions.toArray(new AttributeDefinition[definitions.size()]));
+ }
+
+ public SecureDeploymentWriteAttributeHandler(AttributeDefinition... definitions) {
+ super(definitions);
+ }
+
+ @Override
+ protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<KeycloakAdapterConfigService> hh) throws OperationFailedException {
+ KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.getInstance();
+ hh.setHandback(ckService);
+ ckService.updateSecureDeployment(operation, attributeName, resolvedValue);
+ return false;
+ }
+
+ @Override
+ protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName,
+ ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException {
+ ckService.updateSecureDeployment(operation, attributeName, valueToRestore);
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
new file mode 100755
index 0000000..35c4b3a
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/SharedAttributeDefinitons.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.controller.SimpleAttributeDefinition;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.operations.validation.IntRangeValidator;
+import org.jboss.as.controller.operations.validation.StringLengthValidator;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Defines attributes that can be present in both a realm and an application (secure-deployment).
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class SharedAttributeDefinitons {
+
+ protected static final SimpleAttributeDefinition REALM_PUBLIC_KEY =
+ new SimpleAttributeDefinitionBuilder("realm-public-key", ModelType.STRING, true)
+ .setXmlName("realm-public-key")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition AUTH_SERVER_URL =
+ new SimpleAttributeDefinitionBuilder("auth-server-url", ModelType.STRING, true)
+ .setXmlName("auth-server-url")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition SSL_REQUIRED =
+ new SimpleAttributeDefinitionBuilder("ssl-required", ModelType.STRING, true)
+ .setXmlName("ssl-required")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode("external"))
+ .build();
+ protected static final SimpleAttributeDefinition ALLOW_ANY_HOSTNAME =
+ new SimpleAttributeDefinitionBuilder("allow-any-hostname", ModelType.BOOLEAN, true)
+ .setXmlName("allow-any-hostname")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition DISABLE_TRUST_MANAGER =
+ new SimpleAttributeDefinitionBuilder("disable-trust-manager", ModelType.BOOLEAN, true)
+ .setXmlName("disable-trust-manager")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition TRUSTSTORE =
+ new SimpleAttributeDefinitionBuilder("truststore", ModelType.STRING, true)
+ .setXmlName("truststore")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition TRUSTSTORE_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("truststore-password", ModelType.STRING, true)
+ .setXmlName("truststore-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CONNECTION_POOL_SIZE =
+ new SimpleAttributeDefinitionBuilder("connection-pool-size", ModelType.INT, true)
+ .setXmlName("connection-pool-size")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(0, true))
+ .build();
+
+ protected static final SimpleAttributeDefinition ENABLE_CORS =
+ new SimpleAttributeDefinitionBuilder("enable-cors", ModelType.BOOLEAN, true)
+ .setXmlName("enable-cors")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEYSTORE =
+ new SimpleAttributeDefinitionBuilder("client-keystore", ModelType.STRING, true)
+ .setXmlName("client-keystore")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEYSTORE_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("client-keystore-password", ModelType.STRING, true)
+ .setXmlName("client-keystore-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CLIENT_KEY_PASSWORD =
+ new SimpleAttributeDefinitionBuilder("client-key-password", ModelType.STRING, true)
+ .setXmlName("client-key-password")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_MAX_AGE =
+ new SimpleAttributeDefinitionBuilder("cors-max-age", ModelType.INT, true)
+ .setXmlName("cors-max-age")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(-1, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_ALLOWED_HEADERS =
+ new SimpleAttributeDefinitionBuilder("cors-allowed-headers", ModelType.STRING, true)
+ .setXmlName("cors-allowed-headers")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition CORS_ALLOWED_METHODS =
+ new SimpleAttributeDefinitionBuilder("cors-allowed-methods", ModelType.STRING, true)
+ .setXmlName("cors-allowed-methods")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition EXPOSE_TOKEN =
+ new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true)
+ .setXmlName("expose-token")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition AUTH_SERVER_URL_FOR_BACKEND_REQUESTS =
+ new SimpleAttributeDefinitionBuilder("auth-server-url-for-backend-requests", ModelType.STRING, true)
+ .setXmlName("auth-server-url-for-backend-requests")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition ALWAYS_REFRESH_TOKEN =
+ new SimpleAttributeDefinitionBuilder("always-refresh-token", ModelType.BOOLEAN, true)
+ .setXmlName("always-refresh-token")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition REGISTER_NODE_AT_STARTUP =
+ new SimpleAttributeDefinitionBuilder("register-node-at-startup", ModelType.BOOLEAN, true)
+ .setXmlName("register-node-at-startup")
+ .setAllowExpression(true)
+ .setDefaultValue(new ModelNode(false))
+ .build();
+ protected static final SimpleAttributeDefinition REGISTER_NODE_PERIOD =
+ new SimpleAttributeDefinitionBuilder("register-node-period", ModelType.INT, true)
+ .setXmlName("register-node-period")
+ .setAllowExpression(true)
+ .setValidator(new IntRangeValidator(-1, true))
+ .build();
+ protected static final SimpleAttributeDefinition TOKEN_STORE =
+ new SimpleAttributeDefinitionBuilder("token-store", ModelType.STRING, true)
+ .setXmlName("token-store")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+ protected static final SimpleAttributeDefinition PRINCIPAL_ATTRIBUTE =
+ new SimpleAttributeDefinitionBuilder("principal-attribute", ModelType.STRING, true)
+ .setXmlName("principal-attribute")
+ .setAllowExpression(true)
+ .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true))
+ .build();
+
+
+
+ protected static final List<SimpleAttributeDefinition> ATTRIBUTES = new ArrayList<SimpleAttributeDefinition>();
+ static {
+ ATTRIBUTES.add(REALM_PUBLIC_KEY);
+ ATTRIBUTES.add(AUTH_SERVER_URL);
+ ATTRIBUTES.add(TRUSTSTORE);
+ ATTRIBUTES.add(TRUSTSTORE_PASSWORD);
+ ATTRIBUTES.add(SSL_REQUIRED);
+ ATTRIBUTES.add(ALLOW_ANY_HOSTNAME);
+ ATTRIBUTES.add(DISABLE_TRUST_MANAGER);
+ ATTRIBUTES.add(CONNECTION_POOL_SIZE);
+ ATTRIBUTES.add(ENABLE_CORS);
+ ATTRIBUTES.add(CLIENT_KEYSTORE);
+ ATTRIBUTES.add(CLIENT_KEYSTORE_PASSWORD);
+ ATTRIBUTES.add(CLIENT_KEY_PASSWORD);
+ ATTRIBUTES.add(CORS_MAX_AGE);
+ ATTRIBUTES.add(CORS_ALLOWED_HEADERS);
+ ATTRIBUTES.add(CORS_ALLOWED_METHODS);
+ ATTRIBUTES.add(EXPOSE_TOKEN);
+ ATTRIBUTES.add(AUTH_SERVER_URL_FOR_BACKEND_REQUESTS);
+ ATTRIBUTES.add(ALWAYS_REFRESH_TOKEN);
+ ATTRIBUTES.add(REGISTER_NODE_AT_STARTUP);
+ ATTRIBUTES.add(REGISTER_NODE_PERIOD);
+ ATTRIBUTES.add(TOKEN_STORE);
+ ATTRIBUTES.add(PRINCIPAL_ATTRIBUTE);
+ }
+
+ /**
+ * truststore and truststore-password must be set if ssl-required is not none and disable-trust-manager is false.
+ *
+ * @param attributes The full set of attributes.
+ *
+ * @return <code>true</code> if the attributes are valid, <code>false</code> otherwise.
+ */
+ public static boolean validateTruststoreSetIfRequired(ModelNode attributes) {
+ if (isSet(attributes, DISABLE_TRUST_MANAGER)) {
+ return true;
+ }
+
+ if (isSet(attributes, SSL_REQUIRED) && attributes.get(SSL_REQUIRED.getName()).asString().equals("none")) {
+ return true;
+ }
+
+ return isSet(attributes, TRUSTSTORE) && isSet(attributes, TRUSTSTORE_PASSWORD);
+ }
+
+ private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) {
+ ModelNode attribute = attributes.get(def.getName());
+
+ if (def.getType() == ModelType.BOOLEAN) {
+ return attribute.isDefined() && attribute.asBoolean();
+ }
+
+ return attribute.isDefined() && !attribute.asString().isEmpty();
+ }
+
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakLogger.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakLogger.java
new file mode 100755
index 0000000..292fa65
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakLogger.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.logging;
+
+import org.jboss.logging.BasicLogger;
+import org.jboss.logging.Logger;
+import org.jboss.logging.annotations.LogMessage;
+import org.jboss.logging.annotations.Message;
+import org.jboss.logging.annotations.MessageLogger;
+
+import static org.jboss.logging.Logger.Level.INFO;
+
+/**
+ * This interface to be fleshed out later when error messages are fully externalized.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+@MessageLogger(projectCode = "KEYCLOAK")
+public interface KeycloakLogger extends BasicLogger {
+
+ /**
+ * A logger with a category of the package name.
+ */
+ KeycloakLogger ROOT_LOGGER = Logger.getMessageLogger(KeycloakLogger.class, "org.jboss.keycloak");
+
+ @LogMessage(level = INFO)
+ @Message(value = "Keycloak subsystem override for deployment %s")
+ void deploymentSecured(String deployment);
+
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakMessages.java b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakMessages.java
new file mode 100755
index 0000000..9d456c8
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/logging/KeycloakMessages.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.logging;
+
+import org.jboss.logging.Messages;
+import org.jboss.logging.annotations.MessageBundle;
+
+/**
+ * This interface to be fleshed out later when error messages are fully externalized.
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc.
+ */
+@MessageBundle(projectCode = "KEYCLOAK")
+public interface KeycloakMessages {
+
+ /**
+ * The messages
+ */
+ KeycloakMessages MESSAGES = Messages.getBundle(KeycloakMessages.class);
+}
diff --git a/integration/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/integration/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
new file mode 100644
index 0000000..1f25766
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension
@@ -0,0 +1 @@
+org.keycloak.subsystem.wf8.extension.KeycloakExtension
diff --git a/integration/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties b/integration/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
new file mode 100755
index 0000000..c00bd8d
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/resources/org/keycloak/subsystem/wf8/extension/LocalDescriptions.properties
@@ -0,0 +1,72 @@
+keycloak.subsystem=Keycloak adapter subsystem
+keycloak.subsystem.add=Operation Adds Keycloak adapter subsystem
+keycloak.subsystem.remove=Operation removes Keycloak adapter subsystem
+keycloak.subsystem.realm=A Keycloak realm.
+keycloak.subsystem.secure-deployment=A deployment secured by Keycloak.
+
+keycloak.realm=A Keycloak realm.
+keycloak.realm.add=Add a realm definition to the subsystem.
+keycloak.realm.remove=Remove a realm from the subsystem.
+keycloak.realm.realm-public-key=Public key of the realm
+keycloak.realm.auth-server-url=Base URL of the Realm Auth Server
+keycloak.realm.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.realm.ssl-required=Specify if SSL is required (valid values are all, external and none)
+keycloak.realm.allow-any-hostname=SSL Setting
+keycloak.realm.truststore=Truststore used for adapter client HTTPS requests
+keycloak.realm.truststore-password=Password of the Truststore
+keycloak.realm.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.realm.enable-cors=Enable Keycloak CORS support
+keycloak.realm.client-keystore=n/a
+keycloak.realm.client-keystore-password=n/a
+keycloak.realm.client-key-password=n/a
+keycloak.realm.cors-max-age=CORS max-age header
+keycloak.realm.cors-allowed-headers=CORS allowed headers
+keycloak.realm.cors-allowed-methods=CORS allowed methods
+keycloak.realm.expose-token=Enable secure URL that exposes access token
+keycloak.realm.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
+keycloak.realm.always-refresh-token=Refresh token on every single web request
+keycloak.realm.register-node-at-startup=Cluster setting
+keycloak.realm.register-node-period=how often to re-register node
+keycloak.realm.token-store=cookie or session storage for auth session data
+keycloak.realm.principal-attribute=token attribute to use to set Principal name
+
+
+keycloak.secure-deployment=A deployment secured by Keycloak
+keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak
+keycloak.secure-deployment.realm=Keycloak realm
+keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak
+keycloak.secure-deployment.realm-public-key=Public key of the realm
+keycloak.secure-deployment.auth-server-url=Base URL of the Realm Auth Server
+keycloak.secure-deployment.disable-trust-manager=Adapter will not use a trust manager when making adapter HTTPS requests
+keycloak.secure-deployment.ssl-required=Specify if SSL is required (valid values are all, external and none)
+keycloak.secure-deployment.allow-any-hostname=SSL Setting
+keycloak.secure-deployment.truststore=Truststore used for adapter client HTTPS requests
+keycloak.secure-deployment.truststore-password=Password of the Truststore
+keycloak.secure-deployment.connection-pool-size=Connection pool size for the client used by the adapter
+keycloak.secure-deployment.resource=Application name
+keycloak.secure-deployment.use-resource-role-mappings=Use resource level permissions from token
+keycloak.secure-deployment.credentials=Adapter credentials
+keycloak.secure-deployment.bearer-only=Bearer Token Auth only
+keycloak.secure-deployment.enable-basic-auth=Enable Basic Authentication
+keycloak.secure-deployment.public-client=Public client
+keycloak.secure-deployment.enable-cors=Enable Keycloak CORS support
+keycloak.secure-deployment.client-keystore=n/a
+keycloak.secure-deployment.client-keystore-password=n/a
+keycloak.secure-deployment.client-key-password=n/a
+keycloak.secure-deployment.cors-max-age=CORS max-age header
+keycloak.secure-deployment.cors-allowed-headers=CORS allowed headers
+keycloak.secure-deployment.cors-allowed-methods=CORS allowed methods
+keycloak.secure-deployment.expose-token=Enable secure URL that exposes access token
+keycloak.secure-deployment.auth-server-url-for-backend-requests=URL to use to make background calls to auth server
+keycloak.secure-deployment.always-refresh-token=Refresh token on every single web request
+keycloak.secure-deployment.register-node-at-startup=Cluster setting
+keycloak.secure-deployment.register-node-period=how often to re-register node
+keycloak.secure-deployment.token-store=cookie or session storage for auth session data
+keycloak.secure-deployment.principal-attribute=token attribute to use to set Principal name
+
+keycloak.secure-deployment.credential=Credential value
+
+keycloak.credential=Credential
+keycloak.credential.value=Credential value
+keycloak.credential.add=Credential add
+keycloak.credential.remove=Credential remove
\ No newline at end of file
diff --git a/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd b/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
new file mode 100755
index 0000000..269b323
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="urn:jboss:domain:keycloak:1.1"
+ xmlns="urn:jboss:domain:keycloak:1.1"
+ elementFormDefault="qualified"
+ attributeFormDefault="unqualified"
+ version="1.0">
+
+ <!-- The subsystem root element -->
+ <xs:element name="subsystem" type="subsystem-type"/>
+
+ <xs:complexType name="subsystem-type">
+ <xs:annotation>
+ <xs:documentation>
+ <![CDATA[
+ The Keycloak adapter subsystem, used to register deployments managed by Keycloak
+ ]]>
+ </xs:documentation>
+ </xs:annotation>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="realm" maxOccurs="unbounded" minOccurs="0" type="realm-type"/>
+ <xs:element name="secure-deployment" maxOccurs="unbounded" minOccurs="0" type="secure-deployment-type"/>
+ </xs:choice>
+ </xs:complexType>
+
+ <xs:complexType name="realm-type">
+ <xs:all>
+ <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-cors" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="client-key-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ </xs:all>
+ <xs:attribute name="name" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The name of the realm.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="secure-deployment-type">
+ <xs:all>
+ <xs:element name="client-keystore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-keystore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-cors" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="allow-any-hostname" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="use-resource-role-mappings" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-max-age" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="auth-server-url" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="realm" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="disable-trust-manager" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-methods" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="bearer-only" type="xs:boolean" minOccurs="0" maxOccurs="1" />
+ <xs:element name="cors-allowed-headers" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="resource" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="truststore" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="truststore-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="client-key-password" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="public-client" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="connection-pool-size" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="expose-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="ssl-required" type="xs:string" minOccurs="0" maxOccurs="1" />
+ <xs:element name="realm-public-key" type="xs:string" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="credential" type="credential-type" minOccurs="1" maxOccurs="1"/>
+ <xs:element name="auth-server-url-for-backend-requests" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="always-refresh-token" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-at-startup" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="register-node-period" type="xs:integer" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="token-store" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="principal-attribute" type="xs:string" minOccurs="0" maxOccurs="1"/>
+ <xs:element name="enable-basic-auth" type="xs:boolean" minOccurs="0" maxOccurs="1"/>
+ </xs:all>
+ <xs:attribute name="name" type="xs:string" use="required">
+ <xs:annotation>
+ <xs:documentation>The name of the realm.</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+
+ <xs:complexType name="credential-type">
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="name" type="xs:string" />
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+</xs:schema>
diff --git a/integration/wildfly/wf8-subsystem/src/main/resources/subsystem-templates/keycloak-adapter.xml b/integration/wildfly/wf8-subsystem/src/main/resources/subsystem-templates/keycloak-adapter.xml
new file mode 100644
index 0000000..0abb124
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/main/resources/subsystem-templates/keycloak-adapter.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Template used by WildFly build when directed to include Keycloak subsystem in a configuration. -->
+<config>
+ <extension-module>org.keycloak.keycloak-wf8-subsystem</extension-module>
+ <subsystem xmlns="urn:jboss:domain:keycloak:1.1">
+ </subsystem>
+</config>
diff --git a/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java b/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java
new file mode 100755
index 0000000..1afeec4
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/RealmDefinitionTestCase.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.dmr.ModelNode;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ *
+ * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc.
+ */
+public class RealmDefinitionTestCase {
+
+ private ModelNode model;
+
+ @Before
+ public void setUp() {
+ model = new ModelNode();
+ model.get("realm").set("demo");
+ model.get("resource").set("customer-portal");
+ model.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB");
+ model.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/login");
+ model.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
+ model.get("expose-token").set(true);
+ ModelNode credential = new ModelNode();
+ credential.get("password").set("password");
+ model.get("credentials").set(credential);
+ }
+
+ @Test
+ public void testIsTruststoreSetIfRequired() throws Exception {
+ model.get("ssl-required").set("none");
+ model.get("disable-trust-manager").set(true);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("none");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(true);
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("external");
+ model.get("disable-trust-manager").set(false);
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("all");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ model.get("truststore-password").set("password");
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+
+ model.get("ssl-required").set("external");
+ model.get("disable-trust-manager").set(false);
+ model.get("truststore").set("foo");
+ model.get("truststore-password").set("password");
+ Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model));
+ }
+
+}
diff --git a/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java b/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
new file mode 100755
index 0000000..93e9a59
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/test/java/org/keycloak/subsystem/wf8/extension/SubsystemParsingTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
+ * as indicated by the @author tags. All rights reserved.
+ *
+ * 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.subsystem.wf8.extension;
+
+import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest;
+import org.jboss.dmr.ModelNode;
+import org.junit.Test;
+
+import java.io.IOException;
+
+
+/**
+ * Tests all management expects for subsystem, parsing, marshaling, model definition and other
+ * Here is an example that allows you a fine grained controller over what is tested and how. So it can give you ideas what can be done and tested.
+ * If you have no need for advanced testing of subsystem you look at {@link SubsystemBaseParsingTestCase} that testes same stuff but most of the code
+ * is hidden inside of test harness
+ *
+ * @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
+ * @author Tomaz Cerar
+ * @author <a href="marko.strukelj@gmail.com">Marko Strukelj</a>
+ */
+public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest {
+
+ public SubsystemParsingTestCase() {
+ super(KeycloakExtension.SUBSYSTEM_NAME, new KeycloakExtension());
+ }
+
+ @Test
+ public void testJson() throws Exception {
+ ModelNode node = new ModelNode();
+ node.get("realm").set("demo");
+ node.get("resource").set("customer-portal");
+ node.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB");
+ node.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/login");
+ node.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes");
+ node.get("ssl-required").set("external");
+ node.get("expose-token").set(true);
+ ModelNode credential = new ModelNode();
+ credential.get("password").set("password");
+ node.get("credentials").set(credential);
+
+ System.out.println("json=" + node.toJSONString(false));
+ }
+
+ @Override
+ protected String getSubsystemXml() throws IOException {
+ return readResource("keycloak-1.1.xml");
+ }
+}
diff --git a/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml b/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
new file mode 100644
index 0000000..2d12d88
--- /dev/null
+++ b/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml
@@ -0,0 +1,24 @@
+<subsystem xmlns="urn:jboss:domain:keycloak:1.1">
+ <secure-deployment name="web-console">
+ <realm>master</realm>
+ <resource>web-console</resource>
+ <use-resource-role-mappings>true</use-resource-role-mappings>
+ <realm-public-key>
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
+ </realm-public-key>
+ <auth-server-url>http://localhost:8080/auth</auth-server-url>
+ <ssl-required>EXTERNAL</ssl-required>
+ <credential name="secret">0aa31d98-e0aa-404c-b6e0-e771dba1e798</credential>
+ </secure-deployment>
+ <secure-deployment name="http-endpoint">
+ <realm>master</realm>
+ <resource>http-endpoint</resource>
+ <use-resource-role-mappings>true</use-resource-role-mappings>
+ <realm-public-key>
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB
+ </realm-public-key>
+ <auth-server-url>http://localhost:8080/auth</auth-server-url>
+ <ssl-required>EXTERNAL</ssl-required>
+ <credential name="secret">2769a4a2-5be0-454f-838f-f33b7755b667</credential>
+ </secure-deployment>
+</subsystem>
\ No newline at end of file
diff --git a/model/api/src/main/java/org/keycloak/mappers/MapperConfigValidationException.java b/model/api/src/main/java/org/keycloak/mappers/MapperConfigValidationException.java
new file mode 100644
index 0000000..ca714a9
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/mappers/MapperConfigValidationException.java
@@ -0,0 +1,15 @@
+package org.keycloak.mappers;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MapperConfigValidationException extends Exception {
+
+ public MapperConfigValidationException(String message) {
+ super(message);
+ }
+
+ public MapperConfigValidationException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
index 386ce13..309036c 100644
--- a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
+++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java
@@ -1,10 +1,36 @@
package org.keycloak.mappers;
+import java.util.List;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.provider.ConfiguredProvider;
+import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface UserFederationMapperFactory extends ProviderFactory<UserFederationMapper>, ConfiguredProvider {
+
+ /**
+ * Refers to providerName (type) of the federation provider, which this mapper can be used for. For example "ldap" or "kerberos"
+ *
+ * @return providerName
+ */
+ String getFederationProviderType();
+
+ String getDisplayCategory();
+ String getDisplayType();
+
+ /**
+ * Called when instance of mapperModel is created for this factory through admin endpoint
+ *
+ * @param mapperModel
+ * @throws MapperConfigValidationException if configuration provided in mapperModel is not valid
+ */
+ void validateConfig(UserFederationMapperModel mapperModel) throws MapperConfigValidationException;
+
+ // TODO: Remove this and add realm to the method on ConfiguredProvider?
+ List<ProviderConfigProperty> getConfigProperties(RealmModel realm);
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index 988d9ec..77f825f 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -1,6 +1,7 @@
package org.keycloak.models.utils;
import org.bouncycastle.openssl.PEMWriter;
+import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -8,6 +9,7 @@ import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationMapperModel;
@@ -349,4 +351,31 @@ public final class KeycloakModelUtils {
return mapperModel;
}
+
+ /**
+ * Automatically add "kerberos" to required realm credentials if it's supported by saved provider
+ *
+ * @param realm
+ * @param model
+ * @return true if kerberos credentials were added
+ */
+ public static boolean checkKerberosCredential(RealmModel realm, UserFederationProviderModel model) {
+ String allowKerberosCfg = model.getConfig().get(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION);
+ if (Boolean.valueOf(allowKerberosCfg)) {
+ boolean found = false;
+ List<RequiredCredentialModel> currentCreds = realm.getRequiredCredentials();
+ for (RequiredCredentialModel cred : currentCreds) {
+ if (cred.getType().equals(UserCredentialModel.KERBEROS)) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ realm.addRequiredCredential(UserCredentialModel.KERBEROS);
+ return true;
+ }
+ }
+
+ return false;
+ }
}
pom.xml 12(+11 -1)
diff --git a/pom.xml b/pom.xml
index 5b846b3..5ba74c6 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1081,6 +1081,12 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
+ <artifactId>keycloak-wf8-modules</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
<artifactId>keycloak-server-overlay</artifactId>
<version>${project.version}</version>
<type>zip</type>
@@ -1272,7 +1278,9 @@
<profile>
<id>jboss-earlyaccess-repository</id>
<activation>
- <activeByDefault>true</activeByDefault>
+ <property>
+ <name>!no-jboss-ea-repo</name>
+ </property>
</activation>
<repositories>
<repository>
@@ -1303,6 +1311,7 @@
<id>distribution</id>
<modules>
<module>distribution</module>
+ <module>testsuite/integration-arquillian</module>
</modules>
</profile>
<profile>
@@ -1310,6 +1319,7 @@
<modules>
<module>docbook</module>
<module>distribution</module>
+ <module>testsuite/integration-arquillian</module>
</modules>
<build>
<plugins>
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index c2a4730..e2899fe 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -235,8 +235,8 @@ public class RealmAdminResource {
}
@Path("user-federation")
- public UserFederationResource userFederation() {
- UserFederationResource fed = new UserFederationResource(realm, auth, adminEvent);
+ public UserFederationProvidersResource userFederation() {
+ UserFederationProvidersResource fed = new UserFederationProvidersResource(realm, auth, adminEvent);
ResteasyProviderFactory.getInstance().injectProperties(fed);
//resourceContext.initResource(fed);
return fed;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
new file mode 100644
index 0000000..e5deb3d
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java
@@ -0,0 +1,298 @@
+package org.keycloak.services.resources.admin;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.jboss.logging.Logger;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.jboss.resteasy.spi.NotFoundException;
+import org.keycloak.events.admin.OperationType;
+import org.keycloak.mappers.MapperConfigValidationException;
+import org.keycloak.mappers.UserFederationMapper;
+import org.keycloak.mappers.UserFederationMapperFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationMapperModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.ModelToRepresentation;
+import org.keycloak.models.utils.RepresentationToModel;
+import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.representations.idm.ConfigPropertyRepresentation;
+import org.keycloak.representations.idm.UserFederationMapperRepresentation;
+import org.keycloak.representations.idm.UserFederationMapperTypeRepresentation;
+import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.services.ErrorResponseException;
+import org.keycloak.services.managers.UsersSyncManager;
+import org.keycloak.timer.TimerProvider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class UserFederationProviderResource {
+
+ protected static final Logger logger = Logger.getLogger(UserFederationProviderResource.class);
+
+ private final KeycloakSession session;
+ private final RealmModel realm;
+ private final RealmAuth auth;
+ private final UserFederationProviderModel federationProviderModel;
+ private final AdminEventBuilder adminEvent;
+
+ @Context
+ private UriInfo uriInfo;
+
+ public UserFederationProviderResource(KeycloakSession session, RealmModel realm, RealmAuth auth, UserFederationProviderModel federationProviderModel, AdminEventBuilder adminEvent) {
+ this.session = session;
+ this.realm = realm;
+ this.auth = auth;
+ this.federationProviderModel = federationProviderModel;
+ this.adminEvent = adminEvent;
+ }
+
+ /**
+ * Update a provider
+ *
+ * @param rep
+ */
+ @PUT
+ @NoCache
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void updateProviderInstance(UserFederationProviderRepresentation rep) {
+ auth.requireManage();
+ String displayName = rep.getDisplayName();
+ if (displayName != null && displayName.trim().equals("")) {
+ displayName = null;
+ }
+ UserFederationProviderModel model = new UserFederationProviderModel(rep.getId(), rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
+ rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
+ realm.updateUserFederationProvider(model);
+ new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
+ boolean kerberosCredsAdded = KeycloakModelUtils.checkKerberosCredential(realm, model);
+ if (kerberosCredsAdded) {
+ logger.info("Added 'kerberos' to required realm credentials");
+ }
+
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
+
+ }
+
+ /**
+ * get a provider
+ *
+ */
+ @GET
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public UserFederationProviderRepresentation getProviderInstance() {
+ auth.requireView();
+ return ModelToRepresentation.toRepresentation(this.federationProviderModel);
+ }
+
+ /**
+ * Delete a provider
+ *
+ */
+ @DELETE
+ @NoCache
+ public void deleteProviderInstance() {
+ auth.requireManage();
+
+ realm.removeUserFederationProvider(this.federationProviderModel);
+ new UsersSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), this.federationProviderModel);
+
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
+
+ }
+
+ /**
+ * trigger sync of users
+ *
+ * @return
+ */
+ @POST
+ @Path("sync")
+ @NoCache
+ public UserFederationSyncResult syncUsers(@QueryParam("action") String action) {
+ logger.debug("Syncing users");
+ auth.requireManage();
+
+ UsersSyncManager syncManager = new UsersSyncManager();
+ UserFederationSyncResult syncResult = null;
+ if ("triggerFullSync".equals(action)) {
+ syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), this.federationProviderModel);
+ } else if ("triggerChangedUsersSync".equals(action)) {
+ syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), this.federationProviderModel);
+ }
+
+ adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success();
+ return syncResult;
+ }
+
+ /**
+ * List of available User Federation mapper types
+ *
+ * @return
+ */
+ @GET
+ @Path("mapper-types")
+ @NoCache
+ public Map<String, UserFederationMapperTypeRepresentation> getMapperTypes() {
+ this.auth.requireView();
+ KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+ Map<String, UserFederationMapperTypeRepresentation> types = new HashMap<>();
+ List<ProviderFactory> factories = sessionFactory.getProviderFactories(UserFederationMapper.class);
+
+ for (ProviderFactory factory : factories) {
+ UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory)factory;
+ if (mapperFactory.getFederationProviderType().equals(this.federationProviderModel.getProviderName())) {
+
+ UserFederationMapperTypeRepresentation rep = new UserFederationMapperTypeRepresentation();
+ rep.setId(mapperFactory.getId());
+ rep.setCategory(mapperFactory.getDisplayCategory());
+ rep.setName(mapperFactory.getDisplayType());
+ rep.setHelpText(mapperFactory.getHelpText());
+ List<ProviderConfigProperty> configProperties = mapperFactory.getConfigProperties(realm);
+ for (ProviderConfigProperty prop : configProperties) {
+ ConfigPropertyRepresentation propRep = new ConfigPropertyRepresentation();
+ propRep.setName(prop.getName());
+ propRep.setLabel(prop.getLabel());
+ propRep.setType(prop.getType());
+ propRep.setDefaultValue(prop.getDefaultValue());
+ propRep.setHelpText(prop.getHelpText());
+ rep.getProperties().add(propRep);
+ }
+ types.put(rep.getId(), rep);
+ }
+ }
+ return types;
+ }
+
+ /**
+ * Get mappers configured for this provider
+ *
+ * @return
+ */
+ @GET
+ @Path("mappers")
+ @Produces(MediaType.APPLICATION_JSON)
+ @NoCache
+ public List<UserFederationMapperRepresentation> getMappers() {
+ this.auth.requireView();
+ List<UserFederationMapperRepresentation> mappers = new LinkedList<>();
+ for (UserFederationMapperModel model : realm.getUserFederationMappersByFederationProvider(this.federationProviderModel.getId())) {
+ mappers.add(ModelToRepresentation.toRepresentation(realm, model));
+ }
+ return mappers;
+ }
+
+ /**
+ * Create mapper
+ *
+ * @param mapper
+ * @return
+ */
+ @POST
+ @Path("mappers")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response addMapper(UserFederationMapperRepresentation mapper) {
+ auth.requireManage();
+ UserFederationMapperModel model = RepresentationToModel.toModel(realm, mapper);
+
+ validateModel(model);
+
+ model = realm.addUserFederationMapper(model);
+
+ adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId())
+ .representation(mapper).success();
+
+ return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
+
+ }
+
+ /**
+ * Get mapper
+ *
+ * @param id mapperId
+ * @return
+ */
+ @GET
+ @NoCache
+ @Path("mappers/{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public UserFederationMapperRepresentation getMapperById(@PathParam("id") String id) {
+ auth.requireView();
+ UserFederationMapperModel model = realm.getUserFederationMapperById(id);
+ if (model == null) throw new NotFoundException("Model not found");
+ return ModelToRepresentation.toRepresentation(realm, model);
+ }
+
+ /**
+ * Update mapper
+ *
+ * @param id
+ * @param rep
+ */
+ @PUT
+ @NoCache
+ @Path("mappers/{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void update(@PathParam("id") String id, UserFederationMapperRepresentation rep) {
+ auth.requireManage();
+ UserFederationMapperModel model = realm.getUserFederationMapperById(id);
+ if (model == null) throw new NotFoundException("Model not found");
+ model = RepresentationToModel.toModel(realm, rep);
+
+ validateModel(model);
+
+ realm.updateUserFederationMapper(model);
+ adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success();
+
+ }
+
+ /**
+ * Delete mapper with given ID
+ *
+ * @param id
+ */
+ @DELETE
+ @NoCache
+ @Path("mappers/{id}")
+ public void delete(@PathParam("id") String id) {
+ auth.requireManage();
+ UserFederationMapperModel model = realm.getUserFederationMapperById(id);
+ if (model == null) throw new NotFoundException("Model not found");
+ realm.removeUserFederationMapper(model);
+ adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success();
+
+ }
+
+ private void validateModel(UserFederationMapperModel model) {
+ try {
+ UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationMapper.class, model.getFederationMapperType());
+ mapperFactory.validateConfig(model);
+ } catch (MapperConfigValidationException ex) {
+ throw new ErrorResponseException("Validation error", ex.getMessage(), Response.Status.BAD_REQUEST);
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 6daf649..d1545f2 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -892,6 +892,13 @@ public class LoginActionsService {
ClientSessionModel clientSession = accessCode.getClientSession();
String username = formData.getFirst("username");
+ if(username == null || username.isEmpty()) {
+ event.error(Errors.USERNAME_MISSING);
+ return session.getProvider(LoginFormsProvider.class)
+ .setError(Messages.MISSING_USERNAME)
+ .setClientSessionCode(accessCode.getCode())
+ .createPasswordReset();
+ }
ClientModel client = clientSession.getClient();
if (client == null) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
index ddda4ff..d3dd9ab 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationTestUtils.java
@@ -91,7 +91,7 @@ class FederationTestUtils {
}
public static void addZipCodeLDAPMapper(RealmModel realm, UserFederationProviderModel providerModel) {
- UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel("zipCodeMapper", providerModel.getId(), UserAttributeLDAPFederationMapperFactory.ID,
+ UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel("zipCodeMapper", providerModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "postal_code",
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.POSTAL_CODE,
UserAttributeLDAPFederationMapper.READ_ONLY, "false");
@@ -104,7 +104,7 @@ class FederationTestUtils {
mapperModel.getConfig().put(RoleLDAPFederationMapper.MODE, mode.toString());
realm.updateUserFederationMapper(mapperModel);
} else {
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("realmRolesMapper", providerModel.getId(), RoleLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("realmRolesMapper", providerModel.getId(), RoleLDAPFederationMapperFactory.PROVIDER_ID,
RoleLDAPFederationMapper.ROLES_DN, "ou=RealmRoles,dc=keycloak,dc=org",
RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING, "true",
RoleLDAPFederationMapper.MODE, mode.toString());
@@ -116,7 +116,7 @@ class FederationTestUtils {
mapperModel.getConfig().put(RoleLDAPFederationMapper.MODE, mode.toString());
realm.updateUserFederationMapper(mapperModel);
} else {
- mapperModel = KeycloakModelUtils.createUserFederationMapperModel("financeRolesMapper", providerModel.getId(), RoleLDAPFederationMapperFactory.ID,
+ mapperModel = KeycloakModelUtils.createUserFederationMapperModel("financeRolesMapper", providerModel.getId(), RoleLDAPFederationMapperFactory.PROVIDER_ID,
RoleLDAPFederationMapper.ROLES_DN, "ou=FinanceRoles,dc=keycloak,dc=org",
RoleLDAPFederationMapper.USE_REALM_ROLES_MAPPING, "false",
RoleLDAPFederationMapper.CLIENT_ID, "finance",
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 68abb4a..ed94502 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -324,6 +324,25 @@ public class ResetPasswordTest {
events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).user((String) null).session((String) null).detail(Details.USERNAME, "invalid").removeDetail(Details.EMAIL).removeDetail(Details.CODE_ID).error("user_not_found").assertEvent();
}
+
+ @Test
+ public void resetPasswordMissingUsername() throws IOException, MessagingException, InterruptedException {
+ loginPage.open();
+ loginPage.resetPassword();
+
+ resetPasswordPage.assertCurrent();
+
+ resetPasswordPage.changePassword("");
+
+ resetPasswordPage.assertCurrent();
+
+ assertEquals("Please specify username.", resetPasswordPage.getErrorMessage());
+
+ assertEquals(0, greenMail.getReceivedMessages().length);
+
+ events.expectRequiredAction(EventType.SEND_RESET_PASSWORD).client((String) null).user((String) null).session((String) null).clearDetails().error("username_missing").assertEvent();
+
+ }
@Test
public void resetPasswordExpiredCode() throws IOException, MessagingException, InterruptedException {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index 4d20bc8..276698d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -233,7 +233,7 @@ public class ImportTest extends AbstractModelTest {
Assert.assertTrue(fedMappers1.size() == 1);
UserFederationMapperModel fullNameMapper = fedMappers1.iterator().next();
Assert.assertEquals("FullNameMapper", fullNameMapper.getName());
- Assert.assertEquals(FullNameLDAPFederationMapperFactory.ID, fullNameMapper.getFederationMapperType());
+ Assert.assertEquals(FullNameLDAPFederationMapperFactory.PROVIDER_ID, fullNameMapper.getFederationMapperType());
Assert.assertEquals(ldap1.getId(), fullNameMapper.getFederationProviderId());
Assert.assertEquals("cn", fullNameMapper.getConfig().get(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE));
testsuite/integration-arquillian/pom.xml 182(+182 -0)
diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml
new file mode 100644
index 0000000..2435a1a
--- /dev/null
+++ b/testsuite/integration-arquillian/pom.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <parent>
+ <artifactId>keycloak-testsuite-pom</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.3.0.Final-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>arquillian-integration</artifactId>
+ <name>KeyCloak Admin UI TestSuite</name>
+
+ <properties>
+ <browser>phantomjs</browser>
+
+ <arquillian-core.version>1.1.5.Final</arquillian-core.version>
+ <selenium.version>2.45.0</selenium.version>
+ <arquillian-drone.version>1.3.1.Final</arquillian-drone.version>
+ <arquillian-phantomjs.version>1.1.4.Final</arquillian-phantomjs.version>
+ <arquillian-graphene.version>2.0.3.Final</arquillian-graphene.version>
+ <arquillian-wildfly-container.version>8.1.0.Final</arquillian-wildfly-container.version>
+
+ <!-- Used in profile "wildfly-8-remote".
+ Set to "false" if admin password has already been updated after first login. -->
+ <firstAdminLogin>true</firstAdminLogin>
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.arquillian.selenium</groupId>
+ <artifactId>selenium-bom</artifactId>
+ <version>${selenium.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian</groupId>
+ <artifactId>arquillian-bom</artifactId>
+ <version>${arquillian-core.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian.extension</groupId>
+ <artifactId>arquillian-drone-bom</artifactId>
+ <version>${arquillian-drone.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-arquillian-container-remote</artifactId>
+ <version>${arquillian-wildfly-container.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-arquillian-container-managed</artifactId>
+ <version>${arquillian-wildfly-container.version}</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian.junit</groupId>
+ <artifactId>arquillian-junit-container</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian.graphene</groupId>
+ <artifactId>graphene-webdriver</artifactId>
+ <version>${arquillian-graphene.version}</version>
+ <type>pom</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.arquillian.extension</groupId>
+ <artifactId>arquillian-phantom-driver</artifactId>
+ <version>${arquillian-phantomjs.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <profile>
+ <id>wildfly-8-remote</id>
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-arquillian-container-remote</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <shouldDeploy>false</shouldDeploy>
+ <arquillian.launch>wildfly-8-remote</arquillian.launch>
+ <browser>${browser}</browser>
+ <firstAdminLogin>${first.login}</firstAdminLogin>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>wildfly-8-managed</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.wildfly</groupId>
+ <artifactId>wildfly-arquillian-container-managed</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <properties>
+ <install.directory>${project.build.directory}/install</install.directory>
+ <jbossHome>${install.directory}/keycloak-${project.version}</jbossHome>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.10</version>
+ <executions>
+ <execution>
+ <id>unpack</id>
+ <phase>process-test-resources</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-server-dist</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ <overWrite>false</overWrite>
+ </artifactItem>
+ </artifactItems>
+ <outputDirectory>${install.directory}</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>true</overWriteSnapshots>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <systemPropertyVariables>
+ <shouldDeploy>false</shouldDeploy>
+ <arquillian.launch>wildfly-8-managed</arquillian.launch>
+ <browser>${browser}</browser>
+ <jbossHome>${jbossHome}</jbossHome>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+</project>
testsuite/integration-arquillian/README.md 21(+21 -0)
diff --git a/testsuite/integration-arquillian/README.md b/testsuite/integration-arquillian/README.md
new file mode 100644
index 0000000..a81701d
--- /dev/null
+++ b/testsuite/integration-arquillian/README.md
@@ -0,0 +1,21 @@
+Testing admin console with Arquillian
+=====================================
+
+There are currently two ways of running the tests with help of Arquillian.
+
+Remote mode
+----------
+
+Just simply typle `mvn verify` and you are all set. This requires the instance of Wildfly with embedded Keycloak to be already running.
+
+Managed mode
+------------
+
+You need to pass two arguments to Maven, first is location of your Wildfly server with embedded Keycloak and the other is name of the profile.
+
+ mvn verify -Pwildfly-8-managed -DjbossHome=/your/server/location
+
+Browser
+-------
+
+There are currently two supported browsers - PhantomJS and Firefox. PhantomJS is the default one, in order to use Firefox just specify `-Dbrowser=firefox` parameter in the Maven command.
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/AbstractKeyCloakTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/AbstractKeyCloakTest.java
new file mode 100644
index 0000000..aa1be23
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/AbstractKeyCloakTest.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.ui;
+
+import java.util.concurrent.TimeUnit;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.After;
+import org.junit.Before;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+
+/**
+ *
+ * @author Petr Mensik
+ * @param <P>
+ */
+public abstract class AbstractKeyCloakTest<P extends AbstractPage> extends AbstractTest {
+
+ @Page
+ protected P page;
+
+ @Before
+ public void before() {
+ driver.manage().timeouts().setScriptTimeout(10, TimeUnit.SECONDS);
+ driver.manage().window().maximize();
+ loginAsAdmin();
+ }
+
+ @After
+ public void after() {
+ logOut();
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/AbstractTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/AbstractTest.java
new file mode 100644
index 0000000..b05331e
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/AbstractTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.ui;
+
+import org.jboss.arquillian.drone.api.annotation.Drone;
+import org.jboss.arquillian.graphene.page.Page;
+import org.jboss.arquillian.junit.Arquillian;
+import org.junit.runner.RunWith;
+import org.keycloak.testsuite.ui.fragment.Navigation;
+import org.keycloak.testsuite.ui.fragment.MenuPage;
+import org.keycloak.testsuite.ui.page.LoginPage;
+import org.keycloak.testsuite.ui.page.account.PasswordPage;
+import static org.keycloak.testsuite.ui.util.Constants.ADMIN_PSSWD;
+
+import static org.keycloak.testsuite.ui.util.URL.BASE_URL;
+import org.openqa.selenium.WebDriver;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+@RunWith(Arquillian.class)
+public abstract class AbstractTest {
+
+ private static boolean firstAdminLogin = Boolean.parseBoolean(
+ System.getProperty("firstAdminLogin", "true"));
+
+ @Page
+ protected LoginPage loginPage;
+
+ @Page
+ protected PasswordPage passwordPage;
+
+ @Page
+ protected MenuPage menuPage;
+
+ @Page
+ protected Navigation navigation;
+
+ @Drone
+ protected WebDriver driver;
+
+ public void logOut() {
+ menuPage.logOut();
+ }
+
+ public void loginAsAdmin() {
+ driver.get(BASE_URL);
+ loginPage.loginAsAdmin();
+ if (firstAdminLogin) {
+ passwordPage.confirmNewPassword(ADMIN_PSSWD);
+ passwordPage.submit();
+ firstAdminLogin = false;
+ }
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/CreateRealm.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/CreateRealm.java
new file mode 100644
index 0000000..bae764f
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/CreateRealm.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ui.fragment;
+
+import org.jboss.arquillian.drone.api.annotation.Drone;
+import static org.openqa.selenium.By.id;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class CreateRealm {
+
+ @FindBy(css = ".btn-primary")
+ private WebElement primaryButton;
+
+ @Drone
+ private WebDriver driver;
+
+ public void importRealm(String filePath) {
+ driver.findElement(id("import-file")).sendKeys(filePath);
+ primaryButton.click();
+ }
+
+ public void createRealm(String name, boolean on) {
+ driver.findElement(id("name")).sendKeys(name);
+ primaryButton.click();
+ }
+
+ public void createRealm(String name) {
+ createRealm(name, true);
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/FlashMessage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/FlashMessage.java
new file mode 100644
index 0000000..33a84af
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/FlashMessage.java
@@ -0,0 +1,67 @@
+/*
+ * 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.ui.fragment;
+
+import static org.jboss.arquillian.graphene.Graphene.waitGui;
+import org.jboss.arquillian.graphene.fragment.Root;
+
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+import org.openqa.selenium.WebElement;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class FlashMessage {
+
+ @Root
+ private WebElement root;
+
+ public boolean isSuccess() {
+ waitGui().until("Flash message should be success")
+ .element(root)
+ .attribute("class")
+ .contains("success");
+ return root.getAttribute("class").contains("success");
+ }
+
+ public boolean isError() {
+ waitGui().until("Flash message should be error")
+ .element(root)
+ .attribute("class")
+ .contains("error");
+ return root.getAttribute("class").contains("error");
+ }
+
+ public boolean isDanger() {
+ waitGui().until("Flash message should be danger")
+ .element(root)
+ .attribute("class")
+ .contains("danger");
+ return root.getAttribute("class").contains("danger");
+ }
+
+ public String getText() {
+ return root.getText();
+ }
+
+ public void waitUntilPresent() {
+ waitGuiForElement(root, "Flash message should be visible.");
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/MenuPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/MenuPage.java
new file mode 100644
index 0000000..2f53658
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/MenuPage.java
@@ -0,0 +1,81 @@
+/*
+ * 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.ui.fragment;
+
+import java.util.List;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+import org.openqa.selenium.By;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class MenuPage {
+
+ private static final String MENU_LOCATOR = "ul[class='dropdown-menu']";
+
+ @FindBy(css = MENU_LOCATOR)
+ private List<WebElement> menuList;
+
+ @FindBy(css = ".dropdown-toggle")
+ private List<WebElement> toggle;
+
+ public void logOut() {
+ clickOnMenuElement(Menu.USER, "Sign Out");
+ }
+
+ public void goToAccountManagement() {
+ clickOnMenuElement(Menu.USER, "Manage Account");
+ }
+
+ public void switchRealm(String realmName) {
+ clickOnMenuElement(Menu.REALM, realmName);
+ }
+
+ public String getCurrentRealm() {
+ waitGuiForElement(By.cssSelector(MENU_LOCATOR));
+ return toggle.get(1).getText();
+ }
+
+ private void clickOnMenuElement(Menu menuType, String linkText) {
+ int menuOrder = 0;
+ switch(menuType) {
+ case REALM: menuOrder = 1; break;
+ case USER: menuOrder = 0; break;
+ }
+ waitGuiForElement(By.cssSelector(MENU_LOCATOR));
+ if (!menuList.get(menuOrder).isDisplayed())
+ toggle.get(menuOrder).click();
+ for (WebElement item : menuList.get(menuOrder).findElements(By.cssSelector(MENU_LOCATOR + " a"))) {
+ if (item.getText().contains(linkText)) {
+ item.click();
+ return;
+ }
+ }
+ throw new RuntimeException("Could not find menu item containing \"" + linkText + "\"");
+ }
+
+ private enum Menu {
+ USER, REALM
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/Navigation.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/Navigation.java
new file mode 100644
index 0000000..4175c45
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/Navigation.java
@@ -0,0 +1,157 @@
+/*
+ * 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.ui.fragment;
+
+import org.jboss.arquillian.drone.api.annotation.Drone;
+import static org.jboss.arquillian.graphene.Graphene.waitModel;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class Navigation {
+
+ @Drone
+ private WebDriver driver;
+
+ @FindByJQuery("a:contains('Settings')")
+ private WebElement settingsLink;
+
+ @FindByJQuery("a:contains('Users')")
+ private WebElement usersLink;
+
+ @FindByJQuery("a:contains('Roles')")
+ private WebElement rolesLink;
+
+ @FindByJQuery("a:contains('Clients')")
+ private WebElement clientsLink;
+
+ @FindByJQuery("a:contains('OAuth')")
+ private WebElement oauthLink;
+
+ @FindByJQuery("a:contains('Tokens')")
+ private WebElement tokensLink;
+
+ @FindByJQuery("a:contains('Sessions')")
+ private WebElement sessionLink;
+
+ @FindByJQuery("a:contains('Security Defenses')")
+ private WebElement securityLink;
+
+ @FindByJQuery("a:contains('Events')")
+ private WebElement eventsLink;
+
+ @FindByJQuery("a:contains('Login')")
+ private WebElement loginLink;
+
+ @FindByJQuery("a:contains('Themes')")
+ private WebElement themesLink;
+
+ @FindByJQuery("a:contains('Role Mappings')")
+ private WebElement usersRoleMappings;
+
+ @FindByJQuery("a:contains('Add Realm')")
+ private WebElement addRealm;
+
+ @FindByJQuery("a:contains('Credentials')")
+ private WebElement credentials;
+
+ @FindByJQuery("a:contains('Attributes')")
+ private WebElement attributes;
+
+ @FindBy(css = "div h1")
+ private WebElement currentHeader;
+
+ public void selectRealm(String realmName) {
+ driver.findElement(By.linkText(realmName)).click();
+ }
+
+ public void settings() {
+ openPage(settingsLink, "Settings");
+ }
+
+ public void users() {
+ openPage(usersLink, "Users");
+ }
+
+ public void roles() {
+ openPage(rolesLink, "Roles");
+ }
+
+ public void clients() {
+ openPage(clientsLink, "Clients");
+ }
+
+ public void oauth() {
+ openPage(oauthLink, "OAuth Clients");
+ }
+
+ public void tokens() {
+ openPage(tokensLink, "Settings");
+ }
+
+ public void sessions() {
+ openPage(sessionLink, "Sessions");
+ }
+
+ public void security() {
+ openPage(securityLink, "Settings");
+ }
+
+ public void events() {
+ openPage(eventsLink, "Events");
+ }
+
+ public void login() {
+ openPage(loginLink, "Settings");
+ }
+
+ public void themes() {
+ openPage(themesLink, "Settings");
+ }
+
+ public void roleMappings() {
+ openPage(usersRoleMappings, "User");
+ }
+
+ public void addRealm() {
+ openPage(addRealm, "Add Realm");
+ }
+
+ public void credentials() {
+ openPage(credentials, "Settings");
+ }
+
+ public void attributes() {
+ openPage(attributes, "Attributes");
+ }
+
+ private void openPage(WebElement page, String headerText) {
+ waitGuiForElement(page);
+ page.click();
+ waitModel().until().element(currentHeader).text().contains(headerText);
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/OnOffSwitch.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/OnOffSwitch.java
new file mode 100644
index 0000000..3fc3a90
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/OnOffSwitch.java
@@ -0,0 +1,64 @@
+/*
+ * 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.ui.fragment;
+
+import org.jboss.arquillian.graphene.fragment.Root;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.interactions.Actions;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+
+public class OnOffSwitch {
+
+ @Root
+ private WebElement root;
+
+ @ArquillianResource
+ private Actions actions;
+
+ public boolean isEnabled() {
+ return root.findElement(By.tagName("input")).isSelected();
+ }
+
+ private void click() {
+ actions.moveToElement(root.findElements(By.tagName("span")).get(0))
+ .click().build().perform();
+ }
+
+ public void toggle() {
+ click();
+ }
+
+ public void enable() {
+ if(!isEnabled()) {
+ click();
+ }
+ }
+
+ public void disable() {
+ if(isEnabled()) {
+ click();
+ }
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/PickList.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/PickList.java
new file mode 100644
index 0000000..7711585
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/fragment/PickList.java
@@ -0,0 +1,62 @@
+/*
+ * 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.ui.fragment;
+
+import org.jboss.arquillian.graphene.fragment.Root;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+
+public class PickList {
+
+ @Root
+ private WebElement root;
+
+ private Select firstSelect;
+ private Select secondSelect;
+
+ @FindBy(className = "kc-icon-arrow-right")
+ private WebElement rightArrow;
+
+ @FindBy(className = "kc-icon-arrow-left")
+ private WebElement leftArrow;
+
+ public void addItems(String... values) {
+ for(String value : values) {
+ firstSelect.selectByVisibleText(value);
+ }
+ rightArrow.click();
+ }
+
+ public void setFirstSelect(By locator) {
+ firstSelect = new Select(root.findElement(locator));
+ }
+
+ public void setSecondSelect(By locator) {
+ secondSelect = new Select(root.findElement(locator));
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Account.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Account.java
new file mode 100644
index 0000000..f2a2ef0
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Account.java
@@ -0,0 +1,93 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class Account {
+
+ private String username;
+
+ private String email;
+
+ private String lastName;
+
+ private String firstName;
+
+ public Account(String username, String email, String lastName, String firstName) {
+ this.username = username;
+ this.email = email;
+ this.lastName = lastName;
+ this.firstName = firstName;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Account account = (Account) o;
+
+ if (email != null ? !email.equals(account.email) : account.email != null) return false;
+ if (firstName != null ? !firstName.equals(account.firstName) : account.firstName != null) return false;
+ if (lastName != null ? !lastName.equals(account.lastName) : account.lastName != null) return false;
+ if (username != null ? !username.equals(account.username) : account.username != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = username != null ? username.hashCode() : 0;
+ result = 31 * result + (email != null ? email.hashCode() : 0);
+ result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
+ result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Client.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Client.java
new file mode 100644
index 0000000..5172f8f
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Client.java
@@ -0,0 +1,104 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class Client {
+
+ private String clientId;
+ private String name;
+ private boolean enabled;
+ private String accessType;
+ private String uri;
+
+ public Client(String clientId, String uri) {
+ this.name = clientId;
+ this.clientId = clientId;
+ this.uri = uri;
+ this.enabled = true;
+ }
+
+ public Client(String clientId, String name, String uri) {
+ this.clientId = clientId;
+ this.uri = uri;
+ this.enabled = true;
+ this.name = name;
+ }
+
+ public Client() {
+ }
+
+ public Client(String name, String uri, String accessType, boolean enabled) {
+ this.name = name;
+ this.uri = uri;
+ this.accessType = accessType;
+ this.enabled = enabled;
+ }
+
+ public String getName() { return name; }
+
+ public void setName(String name) { this.name = name; }
+
+ public boolean isEnabled() { return enabled; }
+
+ public void setEnabled(boolean enabled) { this.enabled = enabled; }
+
+ public String getAccessType() { return accessType; }
+
+ public void setAccessType(String accessType) { this.accessType = accessType; }
+
+ public String getUri() { return uri; }
+
+ public void setUri(String uri) { this.uri = uri; }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Client that = (Client) o;
+
+ if (enabled != that.enabled) return false;
+ if (accessType != null ? !accessType.equals(that.accessType) : that.accessType != null) return false;
+ if (!name.equals(that.name)) return false;
+ if (!uri.equals(that.uri)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + (enabled ? 1 : 0);
+ result = 31 * result + (accessType != null ? accessType.hashCode() : 0);
+ result = 31 * result + uri.hashCode();
+ return result;
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/PasswordPolicy.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/PasswordPolicy.java
new file mode 100644
index 0000000..6d2c651
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/PasswordPolicy.java
@@ -0,0 +1,39 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public enum PasswordPolicy {
+
+ HASH_ITERATIONS("Hash Iterations"), LENGTH("Length"), DIGITS("Digits"), LOWER_CASE("Lower Case"),
+ UPPER_CASE("Upper Case"), SPECIAL_CHARS("Special Chars");
+
+ private String name;
+
+ private PasswordPolicy(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Provider.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Provider.java
new file mode 100644
index 0000000..869eec2
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Provider.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class Provider {
+
+ public SocialProvider providerName;
+ public String key;
+ public String secret;
+
+ public Provider() {
+ }
+
+ public Provider(SocialProvider providerName, String key, String secret) {
+ this.providerName = providerName;
+ this.key = key;
+ this.secret = secret;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Role.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Role.java
new file mode 100644
index 0000000..efe6178
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Role.java
@@ -0,0 +1,72 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class Role {
+
+ private String name;
+ private boolean composite;
+ private String description;
+
+ public Role() {
+ }
+
+ public Role(String name) {
+ this(name, false, "");
+ }
+
+ public Role(String name, boolean composite) {
+ this(name, composite, "");
+ }
+
+ public Role(String name, boolean composite, String description) {
+ this.name = name;
+ this.composite = composite;
+ this.description = description;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isComposite() {
+ return composite;
+ }
+
+ public void setComposite(boolean composite) {
+ this.composite = composite;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/SocialProvider.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/SocialProvider.java
new file mode 100644
index 0000000..cb13d68
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/SocialProvider.java
@@ -0,0 +1,39 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public enum SocialProvider {
+
+ FACEBOOK("Facebook"), GITHUB("Github"), GOOGLE("Google"), TWITTER("Twitter");
+
+ private String name;
+
+ private SocialProvider(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Theme.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Theme.java
new file mode 100644
index 0000000..0578449
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/Theme.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public enum Theme {
+
+ BASE("base"), KEYCLOAK("keycloak"), PATTERNFLY("patternfly");
+
+ private final String name;
+
+ private Theme(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/User.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/User.java
new file mode 100644
index 0000000..e4be9f5
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/User.java
@@ -0,0 +1,146 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class User {
+
+ private String userName;
+
+ private String password;
+
+ private String email;
+
+ private String firstName;
+
+ private String lastName;
+
+ private boolean userEnabled;
+
+ private boolean emailVerified;
+
+ private String requiredUserActions;
+
+ public User() {
+ this.userEnabled = true;
+ this.emailVerified = false;
+ }
+
+ public User(String userName) {
+ this();
+ this.userName = userName;
+ }
+
+ public User(String userName, String password) {
+ this(userName);
+ this.password = password;
+ }
+
+ public User(String userName, String password, String email) {
+ this(userName, password);
+ this.email = email;
+ }
+
+ public User(String userName, String password, String email, String firstName, String lastName) {
+ this(userName, password, email);
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ public User(String userName, String password, String email, String firstName, String lastName, boolean userEnabled, boolean emailVerified, String requiredUserActions) {
+ this(userName, password, email, firstName, lastName);
+ this.requiredUserActions = requiredUserActions;
+ }
+
+ public User(User user) {
+ this(user.userName, user.password, user.email, user.firstName, user.lastName,
+ user.userEnabled, user.emailVerified, user.requiredUserActions);
+ }
+
+ public String getUserName() { return userName; }
+
+ public void setUserName(String userName) { this.userName = userName; }
+
+ public String getEmail() { return email; }
+
+ public void setEmail(String email) { this.email = email; }
+
+ public String getFirstName() { return firstName; }
+
+ public void setFirstName(String firstName) { this.firstName = firstName; }
+
+ public String getLastName() { return lastName; }
+
+ public void setLastName(String lastName) { this.lastName = lastName; }
+
+ public boolean isUserEnabled() { return userEnabled; }
+
+ public void setUserEnabled(boolean userEnabled) { this.userEnabled = userEnabled; }
+
+ public boolean isEmailVerified() { return emailVerified; }
+
+ public void setEmailVerified(boolean emailVerified) { this.emailVerified = emailVerified; }
+
+ public String getRequiredUserActions() { return requiredUserActions; }
+
+ public void setRequiredUserActions(String requiredUserActions) { this.requiredUserActions = requiredUserActions; }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ User user = (User) o;
+
+ if (emailVerified != user.emailVerified) return false;
+ if (userEnabled != user.userEnabled) return false;
+ if (email != null ? !email.equals(user.email) : user.email != null) return false;
+ if (firstName != null ? !firstName.equals(user.firstName) : user.firstName != null) return false;
+ if (lastName != null ? !lastName.equals(user.lastName) : user.lastName != null) return false;
+ if (requiredUserActions != null ? !requiredUserActions.equals(user.requiredUserActions) : user.requiredUserActions != null)
+ return false;
+ if (!userName.equals(user.userName)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userName.hashCode();
+ result = 31 * result + (email != null ? email.hashCode() : 0);
+ result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
+ result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
+ result = 31 * result + (userEnabled ? 1 : 0);
+ result = 31 * result + (emailVerified ? 1 : 0);
+ result = 31 * result + (requiredUserActions != null ? requiredUserActions.hashCode() : 0);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/UserAction.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/UserAction.java
new file mode 100644
index 0000000..bf6a396
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/model/UserAction.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ui.model;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public enum UserAction {
+
+ UPDATE_PASSWORD("Update Password"), VERIFY_EMAIL("Verify Email"), UPDATE_PROFILE("Update Profile"), CONFIGURE_TOTP("Configure Totp");
+
+ private final String actionName;
+
+ private UserAction(String actionName) {
+ this.actionName = actionName;
+ }
+
+ public String getActionName() {
+ return actionName;
+ }
+
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/AbstractPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/AbstractPage.java
new file mode 100644
index 0000000..8251ab2
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/AbstractPage.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ui.page;
+
+import java.util.List;
+import org.jboss.arquillian.drone.api.annotation.Drone;
+import org.keycloak.testsuite.ui.util.Constants;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class AbstractPage {
+
+ @Drone
+ protected WebDriver driver;
+
+ @FindBy(css = ".btn-danger")
+ protected WebElement dangerButton;
+
+ //@FindByJQuery(".btn-primary:visible")
+ @FindBy(css = ".btn-primary")
+ protected WebElement primaryButton;
+
+ @FindBy(css = ".btn-primary")
+ protected List<WebElement> primaryButtons;
+
+
+ @FindBy(css = ".ng-binding.btn.btn-danger")
+ protected WebElement deleteConfirmationButton;
+
+ public void goToPage(String page) {
+ driver.get(String.format(page, Constants.CURRENT_REALM));
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/AccountPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/AccountPage.java
new file mode 100644
index 0000000..0c8ab50
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/AccountPage.java
@@ -0,0 +1,107 @@
+/*
+ * 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.ui.page.account;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.keycloak.testsuite.ui.model.Account;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class AccountPage extends AbstractPage {
+
+ @FindBy(id = "username")
+ private WebElement username;
+
+ @FindBy(id = "email")
+ private WebElement email;
+
+ @FindBy(id = "lastName")
+ private WebElement lastName;
+
+ @FindBy(id = "firstName")
+ private WebElement firstName;
+
+ @FindByJQuery("button[value='Save']")
+ private WebElement save;
+
+ @FindByJQuery(".nav li:eq(0) a")
+ private WebElement keyclockConsole;
+
+ @FindByJQuery(".nav li:eq(1) a")
+ private WebElement signOutLink;
+
+ @FindByJQuery(".bs-sidebar ul li:eq(0) a")
+ private WebElement accountLink;
+
+ @FindByJQuery(".bs-sidebar ul li:eq(1) a")
+ private WebElement passwordLink;
+
+ @FindByJQuery(".bs-sidebar ul li:eq(2) a")
+ private WebElement authenticatorLink;
+
+ @FindByJQuery(".bs-sidebar ul li:eq(3) a")
+ private WebElement sessionsLink;
+
+
+ public Account getAccount() {
+ return new Account(username.getAttribute("value"), email.getAttribute("value"), lastName.getAttribute("value"), firstName.getAttribute("value"));
+ }
+
+ public void setAccount(Account account) {
+ email.clear();
+ email.sendKeys(account.getEmail());
+ lastName.clear();
+ lastName.sendKeys(account.getLastName());
+ firstName.clear();
+ firstName.sendKeys(account.getFirstName());
+ }
+
+ public void save() {
+ save.click();
+ }
+
+ public void keycloakConsole() {
+ keyclockConsole.click();
+ }
+
+ public void signOut() {
+ signOutLink.click();
+ }
+
+ public void account() {
+ accountLink.click();
+ }
+
+ public void password() {
+ passwordLink.click();
+ }
+
+ public void authenticator() {
+ authenticatorLink.click();
+ }
+
+ public void sessions() {
+ sessionsLink.click();
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/PasswordPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/PasswordPage.java
new file mode 100644
index 0000000..b0a3e9d
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/PasswordPage.java
@@ -0,0 +1,82 @@
+/*
+ * 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.ui.page.account;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class PasswordPage {
+
+ @FindBy(id = "password")
+ private WebElement passwordInput;
+
+ @FindBy(id = "password-new")
+ private WebElement newPasswordInput;
+
+ @FindBy(id = "password-confirm")
+ private WebElement confirmInput;
+
+ @FindByJQuery("button[value='Save']")
+ private WebElement save;
+
+ @FindBy(xpath = "//input[@value='Submit']")
+ private WebElement submit; // on "update password" page, after first login
+
+ public void setPassword(String oldPassword, String newPassword) {
+ passwordInput.clear();
+ passwordInput.sendKeys(oldPassword);
+ confirmNewPassword(newPassword);
+ }
+
+ public void confirmNewPassword(String newPassword) {
+ newPasswordInput.clear();
+ newPasswordInput.sendKeys(newPassword);
+ confirmInput.clear();
+ confirmInput.sendKeys(newPassword);
+ }
+
+ public void setOldPasswordField(String oldPassword) {
+ passwordInput.clear();
+ passwordInput.sendKeys(oldPassword);
+ }
+
+ public void setNewPasswordField(String newPassword) {
+ newPasswordInput.clear();
+ newPasswordInput.sendKeys(newPassword);
+ }
+
+ public void setConfirmField(String confirmPassword) {
+ confirmInput.clear();
+ confirmInput.sendKeys(confirmPassword);
+ }
+
+ public void save() {
+ save.click();
+ }
+
+ public void submit() {
+ submit.click();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/LoginPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/LoginPage.java
new file mode 100644
index 0000000..06ef63e
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/LoginPage.java
@@ -0,0 +1,67 @@
+/*
+ * 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.ui.page;
+
+import static org.keycloak.testsuite.ui.util.Constants.ADMIN_PSSWD;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class LoginPage extends AbstractPage {
+
+ @FindBy(id = "username")
+ private WebElement usernameInput;
+
+ @FindBy(id = "password")
+ private WebElement passwordInput;
+
+ @FindBy(linkText = "Register")
+ private WebElement registerLink;
+
+ @FindBy(id = "kc-header")
+ private WebElement loginPageHeader;
+
+ public void login(String username, String password) {
+ waitGuiForElement(usernameInput, "Login form should be visible");
+ usernameInput.sendKeys(username);
+ passwordInput.sendKeys(password);
+ passwordInput.submit();
+ }
+
+ public void loginAsAdmin() {
+ login("admin", ADMIN_PSSWD);
+ }
+
+ public void goToUserRegistration() {
+ waitGuiForElement(usernameInput, "Login form should be visible");
+ registerLink.click();
+ }
+
+ public String getLoginPageHeaderText() {
+ return loginPageHeader.getText();
+ }
+
+ public WebElement getLoginPageHeader() {
+ return loginPageHeader;
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/RegisterPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/RegisterPage.java
new file mode 100644
index 0000000..1c3ad33
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/RegisterPage.java
@@ -0,0 +1,99 @@
+/*
+ * 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.ui.page;
+
+import org.keycloak.testsuite.ui.model.User;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class RegisterPage extends AbstractPage {
+
+ @FindBy(id = "username")
+ private WebElement usernameInput;
+
+ @FindBy(id = "email")
+ private WebElement emailInput;
+
+ @FindBy(id = "firstName")
+ private WebElement firstNameInput;
+
+ @FindBy(id = "lastName")
+ private WebElement lastNameInput;
+
+ @FindBy(id = "password")
+ private WebElement passwordInput;
+
+ @FindBy(id = "password-confirm")
+ private WebElement passwordConfirmInput;
+
+ @FindBy(css = "span.kc-feedback-text")
+ private WebElement feedbackError;
+
+ @FindBy(css = "div[id='kc-form-options'] span a")
+ private WebElement backToLoginForm;
+
+ public void registerNewUser(User user) {
+ registerNewUser(user, user.getPassword());
+ }
+
+ public void registerNewUser(User user, String confirmPassword) {
+ driver.manage().timeouts().setScriptTimeout(10, TimeUnit.SECONDS);
+ waitGuiForElement(passwordConfirmInput, "Register form should be visible");
+ clearAndType(usernameInput, user.getUserName());
+ clearAndType(firstNameInput, user.getFirstName());
+ clearAndType(lastNameInput, user.getLastName());
+ clearAndType(emailInput, user.getEmail());
+ clearAndType(passwordInput, user.getPassword());
+ clearAndType(passwordConfirmInput, confirmPassword);
+ primaryButton.click();
+ }
+
+ public void clearAndType(WebElement webElement, String text) {
+ webElement.clear();
+ webElement.sendKeys(text);
+ }
+
+ public boolean isInvalidEmail() {
+ waitGuiForElement(feedbackError, "Feedback message should be visible");
+ return feedbackError.getText().equals("Invalid email address.");
+ }
+
+ public boolean isAttributeSpecified(String attribute) {
+ waitGuiForElement(feedbackError, "Feedback message should be visible");
+ return !feedbackError.getText().contains("Please specify " + attribute + ".");
+ }
+
+ public boolean isPasswordSame() {
+ waitGuiForElement(feedbackError, "Feedback message should be visible");
+ return !feedbackError.getText().equals("Password confirmation doesn't match.");
+ }
+
+ public void backToLoginPage() {
+ backToLoginForm.click();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/session/SessionsPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/session/SessionsPage.java
new file mode 100644
index 0000000..d714a64
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/session/SessionsPage.java
@@ -0,0 +1,32 @@
+/*
+ * 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.ui.page.session;
+
+import org.keycloak.testsuite.ui.page.AbstractPage;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class SessionsPage extends AbstractPage {
+
+ public void logoutAllSessions() {
+ primaryButton.click();
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/ClientPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/ClientPage.java
new file mode 100644
index 0000000..890c7e5
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/ClientPage.java
@@ -0,0 +1,129 @@
+/*
+ * 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.ui.page.settings;
+
+import org.keycloak.testsuite.ui.model.Client;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.arquillian.graphene.findby.ByJQuery;
+
+import static org.junit.Assert.assertEquals;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitAjaxForElement;
+import static org.openqa.selenium.By.cssSelector;
+import static org.openqa.selenium.By.linkText;
+import static org.openqa.selenium.By.tagName;
+
+/**
+ *
+ * @author Filip Kisss
+ */
+public class ClientPage extends AbstractPage {
+
+ @FindBy(id = "clientId")
+ private WebElement clientId;
+
+ @FindBy(id = "name")
+ private WebElement nameInput;
+
+ @FindBy(id = "")
+ private WebElement enabledSwitchToggle;
+
+ @FindBy(id = "accessType")
+ private WebElement accessTypeDropDownMenu;
+
+ @FindBy(id = "newRedirectUri")
+ private WebElement redirectUriInput;
+
+ @FindBy(css = "table[class*='table']")
+ private WebElement dataTable;
+
+ @FindBy(css = "input[class*='search']")
+ private WebElement searchInput;
+
+ public void addClient(Client client) {
+ primaryButton.click();
+ waitAjaxForElement(clientId);
+ clientId.sendKeys(client.getClientId());
+ nameInput.sendKeys(client.getName());
+ if (!client.isEnabled()) {
+ enabledSwitchToggle.click();
+ }
+ accessTypeDropDownMenu.sendKeys(client.getAccessType());
+ redirectUriInput.sendKeys(client.getUri());
+ primaryButton.click();
+ }
+
+ public void addUri(String uri) {
+ redirectUriInput.sendKeys(uri);
+ }
+
+ public void removeUri(Client client) {
+ }
+
+ public void confirmAddClient() {
+ primaryButton.click();
+ }
+
+ public void deleteClient(String clientName) {
+ searchInput.sendKeys(clientName);
+ driver.findElement(linkText(clientName)).click();
+ waitAjaxForElement(dangerButton);
+ dangerButton.click();
+ waitAjaxForElement(deleteConfirmationButton);
+ deleteConfirmationButton.click();
+ }
+
+ public Client findClient(String clientName) {
+ waitAjaxForElement(searchInput);
+ searchInput.sendKeys(clientName);
+ List<Client> clients = getAllRows();
+ if (clients.isEmpty()) {
+ return null;
+
+ } else {
+ assertEquals(1, clients.size());
+ return clients.get(0);
+ }
+ }
+
+ private List<Client> getAllRows() {
+ List<Client> rows = new ArrayList<Client>();
+ List<WebElement> allRows = dataTable.findElements(cssSelector("tbody tr"));
+ if (allRows.size() > 1) {
+ for (WebElement rowElement : allRows) {
+ if (rowElement.isDisplayed()) {
+ Client client = new Client();
+ List<WebElement> tds = rowElement.findElements(tagName("td"));
+ client.setClientId(tds.get(0).getText());
+ client.setUri(tds.get(2).getText());
+ rows.add(client);
+ }
+ }
+ }
+ return rows;
+ }
+
+ public void goToCreateClient() {
+ driver.findElements(ByJQuery.selector(".btn.btn-primary")).get(0).click();
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/CredentialsPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/CredentialsPage.java
new file mode 100644
index 0000000..a0455f5
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/CredentialsPage.java
@@ -0,0 +1,71 @@
+/*
+ * 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.ui.page.settings;
+
+import java.util.List;
+import org.jboss.arquillian.graphene.findby.ByJQuery;
+import org.keycloak.testsuite.ui.model.PasswordPolicy;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+/**
+ *
+ * @author Petr Mensik
+ */
+public class CredentialsPage extends AbstractPage {
+
+ @FindBy(tagName = "select")
+ private Select addPolicySelect;
+
+ @FindBy(css = "tr.ng-scope")
+ private List<WebElement> allRows;
+
+ public void addPolicy(PasswordPolicy policy, int value) {
+ addPolicySelect.selectByVisibleText(policy.getName());
+ setPolicyValue(policy, value);
+ primaryButton.click();
+ }
+
+ public void removePolicy(PasswordPolicy policy) {
+ int policyInputLocation = findPolicy(policy);
+ allRows.get(policyInputLocation).findElements(By.tagName("i")).get(0).click();
+ primaryButton.click();
+ }
+
+ public void editPolicy(PasswordPolicy policy, int value) {
+ setPolicyValue(policy, value);
+ primaryButton.click();
+ }
+
+ private void setPolicyValue(PasswordPolicy policy, int value) {
+ int policyInputLocation = findPolicy(policy);
+ allRows.get(policyInputLocation).findElement(By.tagName("input")).sendKeys(String.valueOf(value));
+ }
+
+ private int findPolicy(PasswordPolicy policy) {
+ for (int i = 0; i < allRows.size(); i++) {
+ String policyName = allRows.get(i).findElement(ByJQuery.selector("td:eq(0)")).getText();
+ if(policyName.equals(policy.getName()))
+ return i;
+ }
+ return 0;
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/DefaultRolesPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/DefaultRolesPage.java
new file mode 100644
index 0000000..d76ede6
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/DefaultRolesPage.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ui.page.settings;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.keycloak.testsuite.ui.fragment.PickList;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.keycloak.testsuite.ui.model.Role;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class DefaultRolesPage extends AbstractPage {
+
+ @FindBy(id = "")
+ private PickList realmDefaultRoles;
+
+ @FindBy(id = "")
+ private PickList applicationDefaultRoles;
+
+ @FindBy(id = "applications")
+ private Select applicationsSelect;
+
+ public void addDefaultRealmRoles(String... roles) {
+ realmDefaultRoles.addItems(roles);
+ }
+
+ public void addDefaultRealmRoles(Role... roles) {
+ List<String> roleList = new ArrayList<String>();
+ for(Role role : roles) {
+ roleList.add(role.getName());
+ }
+ addDefaultRealmRoles(((String []) roleList.toArray()));
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/GeneralSettingsPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/GeneralSettingsPage.java
new file mode 100644
index 0000000..bfab15d
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/GeneralSettingsPage.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ui.page.settings;
+
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.keycloak.testsuite.ui.model.Theme;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+
+public class GeneralSettingsPage extends AbstractPage {
+
+ @FindBy(id = "name")
+ private WebElement realmName;
+
+ @FindBy(id = "enabled")
+ private WebElement realmEnabled;
+
+ @FindBy(id = "updateProfileOnInitialSocialLogin")
+ private WebElement updateProfileOnInitialSocialLogin;
+
+ @FindBy(id = "passwordCredentialGrantAllowed")
+ private WebElement passwordCredentialGrantAllowed;
+
+ @FindBy(id = "loginTheme")
+ private Select loginThemeSelect;
+
+ @FindBy(id = "accountTheme")
+ private Select accountThemeSelect;
+
+ @FindBy(id = "adminTheme")
+ private Select adminThemeSelect;
+
+ @FindBy(id = "emailTheme")
+ private Select emailThemeSelect;
+
+ @FindBy(className = "btn btn-primary btn-lg")
+ private WebElement saveButton;
+
+ public void saveSettings() {
+ saveButton.click();
+ }
+
+ public void selectLoginTheme(Theme theme) {
+ loginThemeSelect.selectByVisibleText(theme.getName());
+ }
+
+ public void selecAccountTheme(Theme theme) {
+ accountThemeSelect.selectByVisibleText(theme.getName());
+ }
+
+ public void selectAdminTheme(Theme theme) {
+ adminThemeSelect.selectByVisibleText(theme.getName());
+ }
+
+ public void selectEmailTheme(Theme theme) {
+ emailThemeSelect.selectByVisibleText(theme.getName());
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/LoginSettingsPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/LoginSettingsPage.java
new file mode 100644
index 0000000..84049dc
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/LoginSettingsPage.java
@@ -0,0 +1,63 @@
+/*
+ * 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.ui.page.settings;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.keycloak.testsuite.ui.fragment.OnOffSwitch;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class LoginSettingsPage extends AbstractPage {
+
+ @FindByJQuery("div[class='onoffswitch']:eq(0)")
+ private OnOffSwitch registrationAllowed;
+
+ @FindByJQuery("div[class='onoffswitch']:eq(1)")
+ private OnOffSwitch resetPasswordAllowed;
+
+ @FindByJQuery("div[class='onoffswitch']:eq(2)")
+ private OnOffSwitch rememberMeEnabled;
+
+ @FindByJQuery("div[class='onoffswitch']:eq(3)")
+ private OnOffSwitch verifyEmailEnabled;
+
+ @FindByJQuery("div[class='onoffswitch']:eq(4)")
+ private OnOffSwitch directGrantApiEnabled;
+
+ @FindByJQuery("div[class='onoffswitch']:eq(5)")
+ private OnOffSwitch requireSsl;
+
+ public boolean isUserRegistrationAllowed() {
+ return registrationAllowed.isEnabled();
+ }
+
+ public void enableUserRegistration() {
+ registrationAllowed.enable();
+ primaryButton.click();
+ }
+
+ public void disableUserRegistration() {
+ registrationAllowed.disable();
+ primaryButton.click();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/RolesPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/RolesPage.java
new file mode 100644
index 0000000..1ddaca5
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/RolesPage.java
@@ -0,0 +1,112 @@
+/*
+ * 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.ui.page.settings;
+
+import java.util.ArrayList;
+import java.util.List;
+import static org.junit.Assert.assertEquals;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.keycloak.testsuite.ui.model.Role;
+import static org.openqa.selenium.By.cssSelector;
+import static org.openqa.selenium.By.linkText;
+import static org.openqa.selenium.By.tagName;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.*;
+/**
+ *
+ * @author Petr Mensik
+ */
+public class RolesPage extends AbstractPage {
+
+ @FindBy(css = "input[class*='search']")
+ private WebElement searchInput;
+
+ @FindBy(css = "table[class*='table']")
+ private WebElement dataTable;
+
+ @FindBy(id = "name")
+ private WebElement nameInput;
+
+ @FindBy(id = "description")
+ private WebElement descriptionInput;
+
+ @FindBy(id = "compositeSwitch")
+ private WebElement compositeSwitchToggle;
+
+
+ public boolean isRoleComposite(String roleName) {
+ return findRole(roleName).isComposite();
+ }
+
+ public void addRole(Role role) {
+ primaryButton.click();
+ waitAjaxForElement(nameInput);
+ nameInput.sendKeys(role.getName());
+ if (role.isComposite()) {
+ compositeSwitchToggle.click();
+ }
+ descriptionInput.sendKeys(role.getDescription());
+ primaryButton.click();
+ }
+
+ public Role findRole(String roleName) {
+ searchInput.sendKeys(roleName);
+ List<Role> roles = getAllRows();
+ assertEquals(1, roles.size());
+ return roles.get(0);
+ }
+
+ public void editRole(Role role) {
+ driver.findElement(linkText(role.getName())).click();
+ waitAjaxForElement(nameInput);
+ nameInput.sendKeys(role.getName());
+ if (role.isComposite()) {
+ compositeSwitchToggle.click();
+ }
+ descriptionInput.sendKeys(role.getDescription());
+ primaryButton.click();
+ }
+
+ public void deleteRole(Role role) {
+ driver.findElement(linkText(role.getName())).click();
+ waitAjaxForElement(dangerButton);
+ dangerButton.click();
+ deleteConfirmationButton.click();
+ }
+
+ public void deleteRole(String name) {
+ deleteRole(new Role(name));
+ }
+
+ private List<Role> getAllRows() {
+ List<Role> rows = new ArrayList<Role>();
+ for (WebElement rowElement : dataTable.findElements(cssSelector("tbody tr"))) {
+ Role role = new Role();
+ List<WebElement> tds = rowElement.findElements(tagName("td"));
+ if(!(tds.isEmpty() || tds.get(0).getText().isEmpty())) {
+ role.setName(tds.get(0).getText());
+ role.setComposite(Boolean.valueOf(tds.get(1).getText()));
+ role.setDescription(tds.get(2).getText());
+ rows.add(role);
+ }
+ }
+ return rows;
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/SecurityPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/SecurityPage.java
new file mode 100644
index 0000000..13d6a07
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/SecurityPage.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ui.page.settings;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.keycloak.testsuite.ui.fragment.OnOffSwitch;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.keycloak.testsuite.ui.util.SeleniumUtils;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class SecurityPage extends AbstractPage {
+
+ @FindByJQuery("a:contains('Brute Force Detection')")
+ private WebElement bruteForceProtectionLink;
+
+ @FindByJQuery("div[class='onoffswitch']")
+ private OnOffSwitch protectionEnabled;
+
+ @FindBy(id = "failureFactor")
+ private WebElement failureFactorInput;
+
+ @FindBy(id = "waitIncrement")
+ private WebElement waitIncrementInput;
+
+ @FindBy(id = "waitIncrementUnit")
+ private Select waitIncrementSelect;
+
+ @FindBy(id = "quickLoginCheckMilliSeconds")
+ private WebElement quickLoginCheckInput;
+
+ @FindBy(id = "minimumQuickLoginWait")
+ private WebElement minQuickLoginWaitInput;
+
+ @FindBy(id = "minimumQuickLoginWaitUnit")
+ private Select minQuickLoginWaitSelect;
+
+ @FindBy(id = "maxFailureWait")
+ private WebElement maxWaitInput;
+
+ @FindBy(id = "maxFailureWaitUnit")
+ private Select maxWaitSelect;
+
+ @FindBy(id = "maxDeltaTime")
+ private WebElement failureResetTimeInput;
+
+ @FindBy(id = "maxDeltaTimeUnit")
+ private Select failureResetTimeSelect;
+
+ public void goToAndEnableBruteForceProtectionTab() {
+ SeleniumUtils.waitGuiForElement(bruteForceProtectionLink);
+ bruteForceProtectionLink.click();
+ if(!protectionEnabled.isEnabled()){
+ protectionEnabled.enable();
+ }
+ }
+
+ public void setFailureFactorInput(String value){
+ failureFactorInput.clear();
+ failureFactorInput.sendKeys(value);
+ }
+
+ public void setWaitIncrementInput(String value){
+ waitIncrementInput.clear();
+ waitIncrementInput.sendKeys(value);
+ }
+
+ public void setQuickLoginCheckInput(String value){
+ quickLoginCheckInput.clear();
+ quickLoginCheckInput.sendKeys(value);
+ }
+
+ public void setMinQuickLoginWaitInput(String value){
+ minQuickLoginWaitInput.clear();
+ minQuickLoginWaitInput.sendKeys(value);
+ }
+
+ public void setMaxWaitInput(String value){
+ maxWaitInput.clear();
+ maxWaitInput.sendKeys(value);
+ }
+
+ public void setFailureResetTimeInput(String value){
+ failureResetTimeInput.clear();
+ failureResetTimeInput.sendKeys(value);
+ }
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/SocialSettingsPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/SocialSettingsPage.java
new file mode 100644
index 0000000..ef38396
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/SocialSettingsPage.java
@@ -0,0 +1,88 @@
+/*
+ * 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.ui.page.settings;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import static org.junit.Assert.assertNotNull;
+import org.keycloak.testsuite.ui.model.Provider;
+import org.keycloak.testsuite.ui.model.SocialProvider;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import static org.openqa.selenium.By.tagName;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class SocialSettingsPage extends AbstractPage {
+
+ @FindBy(tagName = "select")
+ private Select newProviderSelect;
+
+ @FindByJQuery("input[class*='form-control']:eq(1)")
+ private WebElement providerKey;
+
+ @FindByJQuery("input[class*='form-control']:eq(2)")
+ private WebElement providerSecret;
+
+ @FindBy(tagName = "tbody")
+ private WebElement providersTable;
+
+ public void addNewProvider(Provider provider) {
+ newProviderSelect.selectByVisibleText(provider.providerName.getName());
+ providerKey.sendKeys(provider.key);
+ providerSecret.sendKeys(provider.secret);
+ primaryButton.click();
+ }
+
+ public void editProvider(SocialProvider oldProvider, Provider newProvider) {
+ Provider p = find(oldProvider);
+ assertNotNull("Provider should have been found", p);
+ System.out.println(p.providerName);
+ }
+
+ public Provider find(SocialProvider provider){
+ List<Provider> list = getAllRows();
+ for(Provider p : list) {
+ if(p.providerName == provider) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ private List<Provider> getAllRows() {
+ List<Provider> rows = new ArrayList<Provider>();
+ for (WebElement rowElement : providersTable.findElements(tagName("tr"))) {
+ Provider provider = new Provider();
+ List<WebElement> tds = rowElement.findElements(tagName("td"));
+ if(!(tds.isEmpty() || tds.get(0).getText().isEmpty())) {
+ provider.providerName = SocialProvider.valueOf(tds.get(0).getText());
+ provider.key = tds.get(1).getText();
+ provider.secret = tds.get(2).getText();
+ rows.add(provider);
+ }
+ }
+ return rows;
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/ThemesSettingsPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/ThemesSettingsPage.java
new file mode 100644
index 0000000..d311d4d
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/ThemesSettingsPage.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ui.page.settings;
+
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElementNotPresent;
+import org.openqa.selenium.By;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class ThemesSettingsPage extends AbstractPage {
+
+ @FindBy(css = "#loginTheme")
+ private Select loginThemeSelect;
+
+ @FindBy(css = "#accountTheme")
+ private Select accountThemeSelect;
+
+ @FindBy(css = "#adminTheme")
+ private Select adminConsoleThemeSelect;
+
+ @FindBy(css = "#emailTheme")
+ private Select emailThemeSelect;
+
+ @FindBy(css = "link[href*='login/keycloak/css/login.css']")
+ private WebElement keycloakTheme;
+
+ public void changeLoginTheme(String themeName){
+ waitGuiForElement(By.id("loginTheme"));
+ loginThemeSelect.selectByVisibleText(themeName);
+ }
+
+ public void changeAccountTheme(String themeName){
+ accountThemeSelect.selectByVisibleText(themeName);
+ }
+
+ public void changeAdminConsoleTheme(String themeName){
+ adminConsoleThemeSelect.selectByVisibleText(themeName);
+ }
+
+ public void changeEmailTheme(String themeName){
+ emailThemeSelect.selectByVisibleText(themeName);
+ }
+
+ public void verifyBaseTheme(){
+ waitGuiForElementNotPresent(keycloakTheme);
+ }
+
+ public void verifyKeycloakTheme(){
+ waitGuiForElement(keycloakTheme);
+ }
+
+ public void saveTheme() {
+ primaryButton.click();
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/TokensPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/TokensPage.java
new file mode 100644
index 0000000..fc8d433
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/TokensPage.java
@@ -0,0 +1,65 @@
+/*
+ * 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.ui.page.settings;
+
+import java.util.concurrent.TimeUnit;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+import org.openqa.selenium.support.ui.Select;
+
+import static java.lang.String.valueOf;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+import static org.apache.commons.lang3.text.WordUtils.capitalize;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class TokensPage extends AbstractPage {
+
+ @FindBy(id = "ssoSessionIdleTimeout")
+ private WebElement sessionTimeout;
+
+ @FindBy(name = "ssoSessionIdleTimeoutUnit")
+ private Select sessionTimeoutUnit;
+
+ @FindBy(id = "ssoSessionMaxLifespan")
+ private WebElement sessionLifespanTimeout;
+
+ @FindBy(name = "ssoSessionMaxLifespanUnit")
+ private Select sessionLifespanTimeoutUnit;
+
+ public void setSessionTimeout(int timeout, TimeUnit unit) {
+ setTimeout(sessionTimeoutUnit, sessionTimeout, timeout, unit);
+ }
+
+ public void setSessionTimeoutLifespan(int time, TimeUnit unit) {
+ setTimeout(sessionLifespanTimeoutUnit, sessionLifespanTimeout, time, unit);
+ }
+
+ private void setTimeout(Select timeoutElement, WebElement unitElement,
+ int timeout, TimeUnit unit) {
+ waitGuiForElement(sessionTimeout);
+ timeoutElement.selectByValue(capitalize(unit.name().toLowerCase()));
+ unitElement.clear();
+ unitElement.sendKeys(valueOf(timeout));
+ primaryButton.click();
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/UserPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/UserPage.java
new file mode 100644
index 0000000..18450b4
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/settings/UserPage.java
@@ -0,0 +1,195 @@
+/*
+ * 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.ui.page.settings;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.keycloak.testsuite.ui.model.User;
+import org.keycloak.testsuite.ui.page.AbstractPage;
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import org.keycloak.testsuite.ui.model.UserAction;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitAjaxForElement;
+import static org.openqa.selenium.By.*;
+import org.openqa.selenium.support.ui.Select;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class UserPage extends AbstractPage {
+
+ @FindBy(id = "username")
+ private WebElement usernameInput;
+
+ @FindBy(id = "email")
+ private WebElement emailInput;
+
+ @FindBy(id = "firstName")
+ private WebElement firstNameInput;
+
+ @FindBy(id = "lastName")
+ private WebElement lastNameInput;
+
+ @FindBy(id = "emailVerified")
+ private WebElement emailVerifiedSwitchToggle;
+
+ @FindBy(css = "label[for='userEnabled']")
+ private WebElement userEnabledSwitchToggle;
+
+ @FindBy(css = "input[class*='select2-input']")
+ private WebElement requiredUserActionsInput;
+
+ @FindByJQuery(".select2-offscreen")
+ private Select actionsSelect;
+
+ @FindBy(id = "password")
+ private WebElement password;
+
+ @FindBy(id = "confirmPassword")
+ private WebElement confirmPassword;
+
+ @FindBy(css = "input[class*='search']")
+ private WebElement searchInput;
+
+ @FindBy(css = "table[class*='table']")
+ private WebElement dataTable;
+
+ @FindByJQuery("button[kc-cancel] ")
+ private WebElement cancel;
+
+ @FindBy(css = "div[class='input-group-addon'] i")
+ private WebElement searchButton;
+
+ public void addUser(User user) {
+ primaryButtons.get(1).click();
+ waitAjaxForElement(usernameInput);
+ usernameInput.sendKeys(user.getUserName());
+ emailInput.sendKeys(user.getEmail());
+ firstNameInput.sendKeys(user.getFirstName());
+ lastNameInput.sendKeys(user.getLastName());
+ if (!user.isUserEnabled()) {
+ userEnabledSwitchToggle.click();
+ }
+ if (user.isEmailVerified()) {
+ emailVerifiedSwitchToggle.click();
+ }
+// requiredUserActionsInput.sendKeys(user.getRequiredUserActions());
+ primaryButton.click();
+ }
+
+ public void addPasswordForUser(User user) {
+ password.sendKeys(user.getPassword());
+ confirmPassword.sendKeys(user.getPassword());
+ dangerButton.click();
+ waitAjaxForElement(deleteConfirmationButton);
+ deleteConfirmationButton.click();
+ }
+
+ public User findUser(String username) {
+ waitAjaxForElement(searchInput);
+ searchInput.sendKeys(username);
+ searchButton.click();
+ List<User> users = getAllRows();
+ if (users.isEmpty()) {
+ return null;
+
+ } else {
+ assertEquals(1, users.size());
+ return users.get(0);
+ }
+ }
+
+ public void editUser(User user) {
+ goToUser(user);
+ waitAjaxForElement(usernameInput);
+ usernameInput.sendKeys(user.getUserName());
+ emailInput.sendKeys(user.getEmail());
+ if (!user.isUserEnabled()) {
+ userEnabledSwitchToggle.click();
+ }
+ if (user.isEmailVerified()) {
+ emailVerifiedSwitchToggle.click();
+ }
+ requiredUserActionsInput.sendKeys(user.getRequiredUserActions());
+ primaryButton.click();
+ }
+
+ public void deleteUser(String username) {
+ searchInput.sendKeys(username);
+ searchButton.click();
+ driver.findElement(linkText(username)).click();
+ waitAjaxForElement(dangerButton);
+ dangerButton.click();
+ waitAjaxForElement(deleteConfirmationButton);
+ deleteConfirmationButton.click();
+ }
+
+ public void cancel() {
+ cancel.click();
+ }
+
+ public void showAllUsers() {
+ primaryButtons.get(0).click();
+ }
+
+ public void goToUser(User user) {
+ dataTable.findElement(linkText(user.getUserName())).click();
+ }
+
+ public void goToUser(String name) {
+ goToUser(new User(name));
+ }
+
+ public void addAction(UserAction action) {
+ actionsSelect.selectByValue(action.name());
+ primaryButton.click();
+ }
+
+ public void removeAction(UserAction action) {
+ actionsSelect.deselectByValue(action.name());
+ primaryButton.click();
+ }
+
+ private List<User> getAllRows() {
+ List<User> users = new ArrayList<User>();
+ List<WebElement> rows = dataTable.findElements(cssSelector("tbody tr"));
+ if (rows.size() > 1) {
+ for (WebElement rowElement : rows) {
+ if (rowElement.isDisplayed()) {
+ User user = new User();
+ List<WebElement> tds = rowElement.findElements(tagName("td"));
+ if (!(tds.isEmpty() || tds.get(0).getText().isEmpty())) {
+ user.setUserName(tds.get(0).getText());
+ user.setLastName(tds.get(1).getText());
+ user.setFirstName(tds.get(2).getText());
+ user.setEmail(tds.get(3).getText());
+ users.add(user);
+ }
+ }
+ }
+ }
+ return users;
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/account/AccountManagementTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/account/AccountManagementTest.java
new file mode 100644
index 0000000..537fee3
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/account/AccountManagementTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ui.test.account;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.After;
+import org.junit.Test;
+
+import static org.keycloak.testsuite.ui.util.Constants.ADMIN_PSSWD;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import org.keycloak.testsuite.ui.fragment.FlashMessage;
+import org.keycloak.testsuite.ui.model.Account;
+import org.keycloak.testsuite.ui.page.account.AccountPage;
+import org.keycloak.testsuite.ui.page.account.PasswordPage;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class AccountManagementTest extends AbstractKeyCloakTest<AccountPage> {
+
+ @FindByJQuery(".alert")
+ private FlashMessage flashMessage;
+
+ @Page
+ private AccountPage accountPage;
+
+ @Page
+ private PasswordPage passwordPage;
+
+ private static final String USERNAME = "admin";
+ private static final String NEW_PASSWORD = "newpassword";
+ private static final String WRONG_PASSWORD = "wrongpassword";
+
+ @Before
+ public void beforeAccountTest() {
+ menuPage.goToAccountManagement();
+ }
+
+ @After
+ public void afterAccountTest() {
+ accountPage.keycloakConsole();
+ }
+
+ @Test
+ public void passwordPageValidationTest() {
+ page.password();
+ passwordPage.save();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isError());
+
+ passwordPage.setPassword(WRONG_PASSWORD, NEW_PASSWORD);
+ passwordPage.save();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isError());
+
+ passwordPage.setOldPasswordField(ADMIN_PSSWD);
+ passwordPage.setNewPasswordField("something");
+ passwordPage.setConfirmField("something else");
+ passwordPage.save();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isError());
+ }
+
+ @Test
+ public void changePasswordTest() {
+ page.password();
+ passwordPage.setPassword(ADMIN_PSSWD, NEW_PASSWORD);
+ passwordPage.save();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ page.signOut();
+ loginPage.login(USERNAME, NEW_PASSWORD);
+ page.password();
+ passwordPage.setPassword(NEW_PASSWORD, ADMIN_PSSWD);
+ passwordPage.save();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ }
+
+ @Test
+ public void accountPageTest() {
+ page.account();
+ Account adminAccount = accountPage.getAccount();
+ assertEquals(adminAccount.getUsername(), USERNAME);
+ adminAccount.setEmail("a@b");
+ adminAccount.setFirstName("John");
+ adminAccount.setLastName("Smith");
+ accountPage.setAccount(adminAccount);
+ accountPage.save();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+
+ page.signOut();
+ loginPage.login(USERNAME, ADMIN_PSSWD);
+
+ page.account();
+ assertEquals(adminAccount, accountPage.getAccount());
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/client/AddNewClientTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/client/AddNewClientTest.java
new file mode 100644
index 0000000..8e0cb19
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/client/AddNewClientTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ui.test.client;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.fragment.FlashMessage;
+import org.keycloak.testsuite.ui.model.Client;
+import org.keycloak.testsuite.ui.page.settings.ClientPage;
+
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import org.junit.Before;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class AddNewClientTest extends AbstractKeyCloakTest<ClientPage> {
+
+ @FindByJQuery(".alert")
+ private FlashMessage flashMessage;
+
+ @Before
+ public void beforeClientTest() {
+ navigation.clients();
+ page.goToCreateClient();
+ }
+
+ @Test
+ public void addNewClientTest() {
+ Client newClient = new Client("testClient1", "http://example.com/*");
+ page.addClient(newClient);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ navigation.clients();
+
+ page.deleteClient(newClient.getName());
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ assertNull(page.findClient(newClient.getName()));
+ }
+
+ @Test
+ public void addNewClientWithBlankNameTest() {
+ Client newClient = new Client("", "http://example.com/*");
+ page.addClient(newClient);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isDanger());
+ }
+
+ @Test
+ public void addNewClientWithBlankUriTest() {
+ Client newClient = new Client("testClient2", "");
+ page.addClient(newClient);
+ page.confirmAddClient();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isDanger());
+
+ page.addUri("http://testUri.com/*");
+ page.confirmAddClient();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+
+ navigation.clients();
+ page.deleteClient(newClient.getName());
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ assertNull(page.findClient(newClient.getName()));
+ }
+
+ @Test
+ public void addNewClientWithTwoUriTest() {
+ Client newClient = new Client("testClient3", "");
+ page.addClient(newClient);
+ page.confirmAddClient();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isDanger());
+
+ page.addUri("http://testUri.com/*");
+ page.addUri("http://example.com/*");
+
+ page.confirmAddClient();
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+
+ navigation.clients();
+ page.deleteClient(newClient.getName());
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ assertNull(page.findClient(newClient.getName()));
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/role/AddNewRoleTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/role/AddNewRoleTest.java
new file mode 100644
index 0000000..7bcd681
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/role/AddNewRoleTest.java
@@ -0,0 +1,94 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package org.keycloak.testsuite.ui.test.role;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.page.settings.RolesPage;
+import org.keycloak.testsuite.ui.model.Role;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import org.keycloak.testsuite.ui.fragment.FlashMessage;
+import org.keycloak.testsuite.ui.page.settings.UserPage;
+import static org.openqa.selenium.By.id;
+import org.openqa.selenium.support.ui.Select;
+
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class AddNewRoleTest extends AbstractKeyCloakTest<RolesPage> {
+
+ @Page
+ private UserPage userPage;
+
+ @FindByJQuery(".alert")
+ private FlashMessage flashMessage;
+
+ @Before
+ public void beforeTestAddNewRole() {
+ navigation.roles();
+ }
+
+ @Test
+ public void testAddNewRole() {
+ Role role = new Role("role1");
+ page.addRole(role);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ navigation.roles();
+ assertEquals("role1", page.findRole(role.getName()).getName());
+ page.deleteRole(role);
+ }
+
+ @Ignore
+ @Test
+ public void testAddNewRoleWithLongName() {
+ String name = "hjewr89y1894yh98(*&*&$jhjkashd)*(&y8934h*&@#hjkahsdj";
+ page.addRole(new Role(name));
+ assertNotNull(page.findRole(name));
+ navigation.roles();
+ page.deleteRole(name);
+ }
+
+ @Test
+ public void testAddExistingRole() {
+ Role role = new Role("role2");
+ page.addRole(role);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ navigation.roles();
+ page.addRole(role);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isDanger());
+ navigation.roles();
+ page.deleteRole(role);
+ }
+
+ @Test
+ public void testRoleIsAvailableForUsers() {
+ Role role = new Role("User role");
+ page.addRole(role);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ navigation.users();
+ userPage.showAllUsers();
+ userPage.goToUser("admin");
+ navigation.roleMappings();
+ Select rolesSelect = new Select(driver.findElement(id("available")));
+ assertEquals("User role should be present in admin role mapping",
+ role.getName(), rolesSelect.getOptions().get(0).getText());
+ navigation.roles();
+ page.deleteRole(role);
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/session/SessionsTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/session/SessionsTest.java
new file mode 100644
index 0000000..41c04c0
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/session/SessionsTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.ui.test.session;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import org.keycloak.testsuite.ui.page.session.SessionsPage;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class SessionsTest extends AbstractKeyCloakTest<SessionsPage> {
+
+ @Before
+ public void beforeSessionTest() {
+ navigation.sessions();
+ }
+
+ @Test
+ public void testLogoutAllSessions() {
+ page.logoutAllSessions();
+ waitGuiForElement(loginPage.getLoginPageHeader(), "Home page should be visible after logout");
+ loginPage.loginAsAdmin();
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/session/TokensTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/session/TokensTest.java
new file mode 100644
index 0000000..ba1c2e6
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/session/TokensTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ui.test.session;
+
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.page.settings.TokensPage;
+
+import static org.jboss.arquillian.graphene.Graphene.waitModel;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import static org.keycloak.testsuite.ui.util.SeleniumUtils.waitGuiForElement;
+
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class TokensTest extends AbstractKeyCloakTest<TokensPage> {
+
+ private static final int TIMEOUT = 10;
+ private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
+
+ @Before
+ public void beforeTokensTest() {
+ navigation.tokens();
+ }
+
+ @Test
+ public void testTimeoutForRealmSession() throws InterruptedException {
+ page.setSessionTimeout(TIMEOUT, TIME_UNIT);
+ TIME_UNIT.sleep(TIMEOUT + 2); //add 2 secs to timeout
+ driver.navigate().refresh();
+ waitGuiForElement(loginPage.getLoginPageHeader(), "Home page should be visible after session timeout");
+ loginPage.loginAsAdmin();
+ page.setSessionTimeout(30, TimeUnit.MINUTES);
+ }
+
+ @Test
+ public void testLifespanOfRealmSession() {
+ page.setSessionTimeoutLifespan(TIMEOUT, TIME_UNIT);
+ logOut();
+ loginAsAdmin();
+ waitModel().withTimeout(TIMEOUT + 2, TIME_UNIT) //adds 2 seconds to the timeout
+ .pollingEvery(1, TIME_UNIT)
+ .until("Home page should be visible after session timeout")
+ .element(loginPage.getLoginPageHeader())
+ .is()
+ .present();
+ loginPage.loginAsAdmin();
+ navigation.tokens();
+ page.setSessionTimeoutLifespan(10, TimeUnit.HOURS);
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/CredentialsTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/CredentialsTest.java
new file mode 100644
index 0000000..f016db1
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/CredentialsTest.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.ui.test.settings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import org.keycloak.testsuite.ui.model.PasswordPolicy;
+import org.keycloak.testsuite.ui.page.settings.CredentialsPage;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class CredentialsTest extends AbstractKeyCloakTest<CredentialsPage> {
+
+ @Before
+ public void beforeCredentialsTest() {
+ navigation.credentials();
+ }
+
+ @Test
+ public void testDigitsNumber() {
+ page.addPolicy(PasswordPolicy.HASH_ITERATIONS, 5);
+ page.removePolicy(PasswordPolicy.DIGITS);
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/SecuritySettingsTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/SecuritySettingsTest.java
new file mode 100644
index 0000000..b748bd6
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/SecuritySettingsTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ui.test.settings;
+
+import org.junit.Test;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import org.keycloak.testsuite.ui.page.settings.SecurityPage;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class SecuritySettingsTest extends AbstractKeyCloakTest<SecurityPage>{
+
+ @Test
+ public void securitySettingsTest() {
+ navigation.security();
+ page.goToAndEnableBruteForceProtectionTab();
+ //TODO:
+
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/SocialSettingsTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/SocialSettingsTest.java
new file mode 100644
index 0000000..71ecc4e
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/SocialSettingsTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ui.test.settings;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.page.settings.SocialSettingsPage;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import org.keycloak.testsuite.ui.fragment.FlashMessage;
+import org.keycloak.testsuite.ui.model.Provider;
+import org.keycloak.testsuite.ui.model.SocialProvider;
+import org.keycloak.testsuite.ui.util.URL;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class SocialSettingsTest extends AbstractKeyCloakTest<SocialSettingsPage> {
+
+ @FindByJQuery(".alert")
+ private FlashMessage flashMessage;
+
+// @Test
+ public void testAddNewProvider() {
+ page.addNewProvider(new Provider(SocialProvider.FACEBOOK, "klic", "secret"));
+ flashMessage.waitUntilPresent();
+ assertTrue("Success message should be displayed", flashMessage.isSuccess());
+ }
+
+// @Test(expected = NoSuchElementException.class)
+ public void testDuplicitProvider() {
+ page.addNewProvider(new Provider(SocialProvider.FACEBOOK, "a", "b"));
+ }
+
+// @Test
+ public void testEditProvider() {
+ page.goToPage(URL.SETTINGS_SOCIAL);
+ page.editProvider(SocialProvider.FACEBOOK, new Provider(SocialProvider.FACEBOOK, "abc", "def"));
+ }
+
+// @Test
+ public void testDeleteProvider() {
+
+ }
+
+ @Test
+ public void testAddMultipleProviders() {
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/ThemesSettingsTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/ThemesSettingsTest.java
new file mode 100644
index 0000000..c4686ff
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/settings/ThemesSettingsTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.ui.test.settings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import org.keycloak.testsuite.ui.model.Theme;
+import org.keycloak.testsuite.ui.page.settings.ThemesSettingsPage;
+
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class ThemesSettingsTest extends AbstractKeyCloakTest<ThemesSettingsPage> {
+
+ @Before
+ public void beforeThemeTest() {
+ navigation.themes();
+ }
+
+ @Test
+ public void changeLoginThemeTest() {
+ page.changeLoginTheme(Theme.BASE.getName());
+ page.saveTheme();
+ logOut();
+ page.verifyBaseTheme();
+
+ loginAsAdmin();
+ navigation.themes();
+ page.changeLoginTheme(Theme.KEYCLOAK.getName());
+ page.saveTheme();
+ logOut();
+ page.verifyKeycloakTheme();
+
+ loginAsAdmin();
+ }
+
+}
\ No newline at end of file
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/user/AddNewUserTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/user/AddNewUserTest.java
new file mode 100644
index 0000000..5694c51
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/user/AddNewUserTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.ui.test.user;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.fragment.FlashMessage;
+import org.keycloak.testsuite.ui.model.User;
+import org.keycloak.testsuite.ui.page.settings.UserPage;
+
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import static org.keycloak.testsuite.ui.util.Users.TEST_USER1;
+
+/**
+ *
+ * @author Filip Kiss
+ */
+public class AddNewUserTest extends AbstractKeyCloakTest<UserPage> {
+
+ @FindByJQuery(".alert")
+ private FlashMessage flashMessage;
+
+ @Before
+ public void beforeAddNewUserTest() {
+ navigation.users();
+ }
+
+ @Test
+ public void addUserWithInvalidEmailTest() {
+ String testUsername = "testUserInvEmail";
+ String invalidEmail = "user.redhat.com";
+ User testUser = new User(testUsername, "pass", invalidEmail);
+ page.addUser(testUser);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isDanger());
+ navigation.users();
+ assertNull(page.findUser(testUsername));
+ }
+
+ @Test
+ public void addUserWithNoUsernameTest() {
+ User testUser = new User();
+ page.addUser(testUser);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isDanger());
+ }
+
+ @Ignore
+ @Test
+ public void addUserWithLongNameTest() {
+ String longUserName = "thisisthelongestnameeveranditcannotbeusedwhencreatingnewuserinkeycloak";
+ User testUser = new User(longUserName);
+ navigation.users();
+ page.addUser(testUser);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isDanger());
+ assertNull(page.findUser(testUser.getUserName()));
+ }
+
+ @Test
+ public void addDuplicatedUser() {
+ String testUsername = "test_duplicated_user";
+ User testUser = new User(testUsername);
+ page.addUser(testUser);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ navigation.users();
+ assertNotNull(page.findUser(testUsername));
+
+ User testUser2 = new User(testUsername);
+ page.addUser(testUser2);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isDanger());
+ navigation.users();
+ page.deleteUser(testUsername);
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ assertNull(page.findUser(testUser2.getUserName()));
+ }
+
+ @Test
+ public void addDisabledUser() {
+ page.addUser(TEST_USER1);
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ navigation.users();
+ page.deleteUser(TEST_USER1.getUserName());
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ assertNull(page.findUser(TEST_USER1.getUserName()));
+ }
+
+
+
+
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/user/RegisterNewUserTest.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/user/RegisterNewUserTest.java
new file mode 100644
index 0000000..834ba25
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/test/user/RegisterNewUserTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ui.test.user;
+
+import org.jboss.arquillian.graphene.findby.FindByJQuery;
+import org.jboss.arquillian.graphene.page.Page;
+import org.junit.After;
+import org.junit.Test;
+import org.keycloak.testsuite.ui.fragment.FlashMessage;
+import org.keycloak.testsuite.ui.model.User;
+import org.keycloak.testsuite.ui.page.RegisterPage;
+import org.keycloak.testsuite.ui.page.settings.UserPage;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.keycloak.testsuite.ui.AbstractKeyCloakTest;
+import org.keycloak.testsuite.ui.page.settings.LoginSettingsPage;
+import static org.keycloak.testsuite.ui.util.Users.*;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class RegisterNewUserTest extends AbstractKeyCloakTest<RegisterPage> {
+
+ @Page
+ private UserPage userPage;
+
+ @Page
+ private LoginSettingsPage loginSettingsPage;
+
+ @FindByJQuery(".alert")
+ private FlashMessage flashMessage;
+
+ @Before
+ public void beforeUserRegistration() {
+ navigation.settings();
+ navigation.login();
+ loginSettingsPage.enableUserRegistration();
+ logOut();
+ loginPage.goToUserRegistration();
+ }
+
+ @After
+ public void afterUserRegistration() {
+ navigation.settings();
+ navigation.login();
+ loginSettingsPage.disableUserRegistration();
+ }
+
+ @Test
+ public void registerNewUserTest() {
+ page.registerNewUser(TEST_USER1);
+ logOut();
+ loginAsAdmin();
+ navigation.users();
+ userPage.deleteUser(TEST_USER1.getUserName());
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ }
+
+
+ @Test
+ public void registerNewUserWithWrongEmail() {
+ User testUser = new User(TEST_USER1);
+ testUser.setEmail("newUser.redhat.com");
+ page.registerNewUser(testUser);
+ assertTrue(page.isInvalidEmail());
+ page.backToLoginPage();
+ loginAsAdmin();
+ navigation.users();
+ assertNull(userPage.findUser(testUser.getUserName()));
+ }
+
+ @Test
+ public void registerNewUserWithWrongAttributes() {
+ User testUser = new User();
+
+ page.registerNewUser(testUser);
+ assertFalse(page.isAttributeSpecified("first name"));
+ testUser.setFirstName("name");
+ page.registerNewUser(testUser);
+ assertFalse(page.isAttributeSpecified("last name"));
+ testUser.setLastName("surname");
+ page.registerNewUser(testUser);
+ assertFalse(page.isAttributeSpecified("email"));
+ testUser.setEmail("mail@redhat.com");
+ page.registerNewUser(testUser);
+ assertFalse(page.isAttributeSpecified("username"));
+ testUser.setUserName("user");
+ page.registerNewUser(testUser);
+ assertFalse(page.isAttributeSpecified("password"));
+ testUser.setPassword("password");
+ page.registerNewUser(testUser);
+ logOut();
+ loginAsAdmin();
+ navigation.users();
+ userPage.deleteUser(TEST_USER1.getUserName());
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ }
+
+ @Test
+ public void registerNewUserWithNotMatchingPasswords() {
+ page.registerNewUser(TEST_USER1, "psswd");
+ assertFalse(page.isPasswordSame());
+ page.registerNewUser(TEST_USER1);
+ logOut();
+ loginAsAdmin();
+ navigation.users();
+ userPage.deleteUser(TEST_USER1.getUserName());
+ flashMessage.waitUntilPresent();
+ assertTrue(flashMessage.getText(), flashMessage.isSuccess());
+ }
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/Constants.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/Constants.java
new file mode 100644
index 0000000..3bdde85
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/Constants.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ui.util;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public final class Constants {
+
+ private Constants() {
+ }
+
+ public static String CURRENT_REALM = "master";
+
+ public static final String ADMIN_PSSWD = "admin";
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/SeleniumUtils.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/SeleniumUtils.java
new file mode 100644
index 0000000..15a4ee5
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/SeleniumUtils.java
@@ -0,0 +1,78 @@
+/*
+ * 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.ui.util;
+
+import static org.jboss.arquillian.graphene.Graphene.waitAjax;
+import static org.jboss.arquillian.graphene.Graphene.waitGui;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public final class SeleniumUtils {
+
+ private SeleniumUtils() {
+ }
+
+ public static void waitAjaxForElement(By element) {
+ waitAjax().until()
+ .element(element)
+ .is()
+ .present();
+ }
+
+ public static void waitAjaxForElement(WebElement element) {
+ waitAjax().until()
+ .element(element)
+ .is()
+ .present();
+ }
+
+ public static void waitGuiForElement(By element, String message) {
+ waitGui().until(message)
+ .element(element)
+ .is()
+ .present();
+ }
+
+ public static void waitGuiForElement(By element) {
+ waitGuiForElement(element, null);
+ }
+
+ public static void waitGuiForElement(WebElement element) {
+ waitGuiForElement(element, null);
+ }
+
+ public static void waitGuiForElement(WebElement element, String message) {
+ waitGui().until(message)
+ .element(element)
+ .is()
+ .present();
+ }
+
+ public static void waitGuiForElementNotPresent(WebElement element) {
+ waitGui().until()
+ .element(element)
+ .is()
+ .not()
+ .present();
+ }
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/URL.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/URL.java
new file mode 100644
index 0000000..ffde485
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/URL.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ui.util;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public class URL {
+
+ public static final String BASE_URL = "http://localhost:8080/auth/admin/master/console/index.html";
+
+ public static String SETTINGS_GENERAL_SETTINGS = BASE_URL + "#/realms/%s";
+ public static String SETTINGS_ROLES = BASE_URL + "#/realms/%s/roles";
+ public static String SETTINGS_LOGIN = BASE_URL + "#/realms/%s/login-settings";
+ public static String SETTINGS_SOCIAL = BASE_URL + "#/realms/%s/social-settings";
+
+}
diff --git a/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/Users.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/Users.java
new file mode 100644
index 0000000..77a6b5f
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/util/Users.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ui.util;
+
+import org.keycloak.testsuite.ui.model.User;
+
+/**
+ *
+ * @author Petr Mensik
+ */
+public final class Users {
+
+ private Users() {
+ }
+
+ public static final User ADMIN = new User("admin", "admin");
+ public static final User EMPTY_USER = new User();
+ public static final User TEST_USER1 = new User("user", "password", "user@redhat.com", "user", "test");
+
+}
diff --git a/testsuite/integration-arquillian/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/src/test/resources/arquillian.xml
new file mode 100644
index 0000000..91393a9
--- /dev/null
+++ b/testsuite/integration-arquillian/src/test/resources/arquillian.xml
@@ -0,0 +1,23 @@
+<arquillian xmlns="http://jboss.org/schema/arquillian"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://jboss.org/schema/arquillian
+ http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
+
+
+ <container qualifier="wildfly-8-remote">
+ <protocol type="jmx-as7">
+ <property name="executionType">REMOTE</property>
+ </protocol>
+ </container>
+
+ <container qualifier="wildfly-8-managed">
+ <configuration>
+ <property name="jbossHome">${jbossHome}</property>
+ <property name="serverConfig">standalone.xml</property>
+ </configuration>
+ </container>
+
+ <extension qualifier="webdriver">
+ <property name="browser">${browser}</property>
+ </extension>
+</arquillian>