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 f03b3765c0..f7f594af0a 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 properties; + protected List properties = new LinkedList<>(); public String getId() { return id; diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml index 612ecae47b..8c13893c61 100755 --- a/distribution/adapters/pom.xml +++ b/distribution/adapters/pom.xml @@ -38,5 +38,6 @@ tomcat7-adapter-zip tomcat8-adapter-zip wildfly-adapter-zip + wf8-adapter diff --git a/distribution/adapters/wf8-adapter/pom.xml b/distribution/adapters/wf8-adapter/pom.xml new file mode 100644 index 0000000000..7f71d64490 --- /dev/null +++ b/distribution/adapters/wf8-adapter/pom.xml @@ -0,0 +1,20 @@ + + + keycloak-parent + org.keycloak + 1.3.0.Final-SNAPSHOT + ../../../pom.xml + + Keycloak Wildfly 8 Adapter + + 4.0.0 + + keycloak-wf8-adapter-dist-pom + pom + + + wf8-modules + wf8-adapter-zip + + 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 0000000000..cad94c5f06 --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-adapter-zip/assembly.xml @@ -0,0 +1,29 @@ + + war-dist + + + zip + tar.gz + + false + + + + ${project.build.directory}/unpacked + + net/iharder/base64/** + org/apache/httpcomponents/** + org/keycloak/keycloak-core/** + org/keycloak/keycloak-adapter-core/** + org/keycloak/keycloak-jboss-adapter-core/** + org/keycloak/keycloak-undertow-adapter/** + org/keycloak/keycloak-wildfly-adapter/** + org/keycloak/keycloak-wf8-subsystem/** + + + **/*.war + + modules/system/layers/base + + + 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 0000000000..aa33dd34dd --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-adapter-zip/pom.xml @@ -0,0 +1,76 @@ + + 4.0.0 + + keycloak-parent + org.keycloak + 1.3.0.Final-SNAPSHOT + ../../../../pom.xml + + + keycloak-wf8-adapter-dist + pom + Keycloak Wildfly 8 Adapter Distro + + + + + org.keycloak + keycloak-wf8-modules + zip + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack + prepare-package + + unpack + + + + + org.keycloak + keycloak-wf8-modules + zip + ${project.build.directory}/unpacked + + + + + + + + maven-assembly-plugin + + + assemble + package + + single + + + + assembly.xml + + + target + + + target/assembly/work + + false + + + + + + + + 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 0000000000..4a34435ac4 --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-modules/assembly.xml @@ -0,0 +1,22 @@ + + dist + + + zip + + false + + + + ../../ + + License.html + + + + + ${project.build.directory}/modules + + + + 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 0000000000..0ede555b6e --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-modules/build.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000..3d9438a4a4 --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-modules/lib.xml @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + project.setProperty("current.maven.root", root); + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "; + 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"; + } + project.setProperty("current.resource.root", root); + ]]> + + + 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 0000000000..d79e42cfda --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-modules/pom.xml @@ -0,0 +1,137 @@ + + + + 4.0.0 + + + keycloak-parent + org.keycloak + 1.3.0.Final-SNAPSHOT + ../../../../pom.xml + + + keycloak-wf8-modules + + Keycloak Wildfly 8 Modules + pom + + + org.keycloak + keycloak-core + + + org.keycloak + keycloak-adapter-core + + + org.keycloak + keycloak-jboss-adapter-core + + + org.keycloak + keycloak-undertow-adapter + + + org.keycloak + keycloak-wildfly-adapter + + + org.keycloak + keycloak-wf8-subsystem + ${project.version} + + + org.apache.httpcomponents + httpmime + + + org.apache.httpcomponents + httpcore + + + + + + + org.apache.maven.plugins + maven-antrun-plugin + false + + + build-dist + + run + + compile + + + + + + + + + + + + org.jboss + jandex + 1.0.3.Final + + + ant-contrib + ant-contrib + 1.0b3 + + + ant + ant + + + + + org.apache.ant + ant-apache-bsf + 1.9.3 + + + org.apache.bsf + bsf-api + 3.1 + + + rhino + js + 1.7R2 + + + + + maven-assembly-plugin + + + assemble + package + + single + + + + assembly.xml + + + target + + + target/assembly/work + + false + + + + + + + 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 0000000000..c99b96879b --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/net/iharder/base64/main/module.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + 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 0000000000..a3e65f8dd0 --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/apache/httpcomponents/4.3/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + 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 0000000000..1be1486fe2 --- /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 @@ + + + + + + + + + + + + + + + + + + + + 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 0000000000..545f16847c --- /dev/null +++ b/distribution/adapters/wf8-adapter/wf8-modules/src/main/resources/modules/org/keycloak/keycloak-core/main/module.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + 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 0000000000..beac07bac7 --- /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 @@ + + + + + + + + + + + + + + + + + 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 0000000000..1772a22cfd --- /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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000..9fdf2b6a58 --- /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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000..2b0e537024 --- /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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/distribution/adapters/wildfly-adapter-zip/assembly.xml b/distribution/adapters/wildfly-adapter-zip/assembly.xml index 9448028cc8..738ad2add8 100755 --- a/distribution/adapters/wildfly-adapter-zip/assembly.xml +++ b/distribution/adapters/wildfly-adapter-zip/assembly.xml @@ -18,7 +18,7 @@ org/keycloak/keycloak-jboss-adapter-core/** org/keycloak/keycloak-undertow-adapter/** org/keycloak/keycloak-wildfly-adapter/** - org/keycloak/keycloak-subsystem/** + org/keycloak/keycloak-adapter-subsystem/** **/*.war 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 8b3d40c4e2..24a5cd8e9f 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 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 7b17e18d53..c111f36e87 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 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 3a0409b9dc..e9dcbb1108 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 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 f259d0c0c4..3e8634332e 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 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 166c5bf27b..c020f5139b 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 6b8f186d4c..33ace78075 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 Marek Posolda */ 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 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 6ef69791dc..d0d623005f 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"; - @Override - public String getHelpText() { - return "Some help text - full name mapper - TODO"; + protected static final List configProperties = new ArrayList(); + + 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 List getConfigProperties() { - return null; + public String getHelpText() { + 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 String getDisplayType() { + return "Full Name"; + } + + @Override + public List 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 47f288dba1..084b255e4a 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 trimmed = new HashSet(); 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 cbae85069f..a5eadd99a7 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"; - @Override - public String getHelpText() { - return "Some help text - role mapper - TODO"; + protected static final List configProperties = new ArrayList(); + + 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 modes = new LinkedList(); + 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 List getConfigProperties() { - return null; + public String getHelpText() { + 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 String getDisplayType() { + return "Role mappings"; + } + + @Override + public List getConfigProperties(RealmModel realm) { + List props = new ArrayList(configProperties); + + Map clients = realm.getClientNameMap(); + List clientIds = new ArrayList(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 564b012491..c0b9d79233 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 configProperties = new ArrayList(); - @Override - public String getHelpText() { - return "Some help text TODO"; + 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 List getConfigProperties() { - return null; + public String getHelpText() { + 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 String getDisplayType() { + return "User Attribute"; + } + + @Override + public List 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 56f600825b..7a0ae1a67e 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 2444be45b3..bc723f31e5 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 3f72ffe049..3a492bbf53 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 e9f09a28a7..f192516355 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 b2c7da1e99..f0d8774f43 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 @@
  • Add User Federation Provider
  • -

    User Federation Provider {{instance.displayName|capitalize}}

    -

    Add User Federation Provider

    +

    {{instance.providerName|capitalize}} User Federation Provider {{instance.displayName|capitalize}}

    +

    Add {{instance.providerName|capitalize}} User Federation Provider

    + +
    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 2294abba82..b2f4701793 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 @@

    Kerberos User Federation Provider {{instance.displayName|capitalize}}

    Add Kerberos User Federation Provider

    + +
    Required Settings 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 ec916a0e1c..2b86f0941b 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 @@

    LDAP User Federation Provider {{instance.displayName|capitalize}}

    Add LDAP User Federation Provider

    + +
    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 0000000000..0b9c144380 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mapper-detail.html @@ -0,0 +1,78 @@ +
    + + +

    User Federation Mapper {{mapper.name}}

    +

    Add User Federation Mapper

    + + +
    +
    + +
    + +
    +
    +
    + +
    + +
    + Name of the mapper. +
    +
    + +
    +
    + +
    +
    + {{mapperType.helpText}} +
    +
    + +
    + +
    + {{mapperType.helpText}} +
    +
    + + +
    + +
    +
    + +
    +
    + +
    + {{option.helpText}} +
    + +
    +
    + + +
    + +
    + + + +
    + +
    + + \ 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 0000000000..d650100b44 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/federated-mappers.html @@ -0,0 +1,53 @@ +
    + + +

    {{provider.providerName === 'ldap' ? 'LDAP' : (provider.providerName|capitalize)}} User Federation Provider {{provider.displayName|capitalize}}

    + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + Create +
    +
    +
    NameCategoryType
    {{mapper.name}}{{mapperTypes[mapper.federationMapperType].category}}{{mapperTypes[mapper.federationMapperType].name}}
    No mappers available
    +
    + + \ No newline at end of file diff --git a/integration/wildfly/pom.xml b/integration/wildfly/pom.xml index 16a4d0331c..3e370c64eb 100644 --- a/integration/wildfly/pom.xml +++ b/integration/wildfly/pom.xml @@ -18,5 +18,6 @@ wildfly-extensions wildfly-server-subsystem wildfly-adapter-subsystem + wf8-subsystem \ No newline at end of file diff --git a/integration/wildfly/wf8-subsystem/pom.xml b/integration/wildfly/wf8-subsystem/pom.xml new file mode 100755 index 0000000000..1d72912b91 --- /dev/null +++ b/integration/wildfly/wf8-subsystem/pom.xml @@ -0,0 +1,115 @@ + + + + 4.0.0 + + + org.keycloak + keycloak-parent + 1.3.0.Final-SNAPSHOT + ../../../pom.xml + + + keycloak-wf8-subsystem + Keycloak Adapter Subsystem + + jar + + + 8.2.0.Final + 8.2.0.Final + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + false + true + + + jboss.home + ${jboss.home} + + + + **/*TestCase.java + + + + + + + + + org.wildfly + wildfly-controller + ${wildfly.version} + provided + + + org.wildfly + wildfly-server + ${wildfly.version} + provided + + + org.wildfly + wildfly-web-common + ${wildfly.version} + provided + + + org.jboss.logging + jboss-logging-annotations + ${jboss-logging-tools.version} + + provided + true + + + + org.jboss.logging + jboss-logging-processor + ${jboss-logging-tools.version} + + provided + true + + + + org.wildfly + wildfly-subsystem-test-framework + ${wildfly.version} + test + + + junit + junit + test + + + org.keycloak + keycloak-wildfly-adapter + ${project.version} + + + 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 0000000000..56b8ef10ca --- /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> 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 0000000000..6083e936e4 --- /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 0000000000..510f3ed570 --- /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 { + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder 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 0000000000..f3f781890c --- /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 0000000000..43992910f1 --- /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 contextParams = webMetaData.getContextParams(); + if (contextParams == null) { + contextParams = new ArrayList(); + } + + 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 0000000000..4843534260 --- /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 realms = new HashMap(); + + // keycloak-secured deployments + private final Map secureDeployments = new HashMap(); + + + 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 0000000000..894f662b87 --- /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 0000000000..7008fb6d5f --- /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 0000000000..6049f10b88 --- /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 0000000000..3be483e5fa --- /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> 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 0000000000..a6093cf26d --- /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 0000000000..efa260b46e --- /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>, XMLElementWriter { + + /** + * {@inheritDoc} + */ + @Override + public void readElement(final XMLExtendedStreamReader reader, final List 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 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 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 credentialsToAdd = new ArrayList(); + 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 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 0000000000..fef809ee92 --- /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> 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 0000000000..628a5d7f80 --- /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 REALM_ONLY_ATTRIBUTES = new ArrayList(); + static { + } + + protected static final List ALL_ATTRIBUTES = new ArrayList(); + static { + ALL_ATTRIBUTES.addAll(REALM_ONLY_ATTRIBUTES); + ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES); + } + + private static final Map DEFINITION_LOOKUP = new HashMap(); + 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 0000000000..84d8ab03b9 --- /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 0000000000..b1062c6073 --- /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 { + + public RealmWriteAttributeHandler(AttributeDefinition... definitions) { + super(definitions); + } + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, HandbackHolder 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 0000000000..66cb8a7cdb --- /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> 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 0000000000..d16a25e7f5 --- /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 DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList(); + 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 ALL_ATTRIBUTES = new ArrayList(); + static { + ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES); + ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES); + } + + private static final Map DEFINITION_LOOKUP = new HashMap(); + 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 0000000000..6629d08205 --- /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 0000000000..3788c1b5d5 --- /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 { + + public SecureDeploymentWriteAttributeHandler(List 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 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 0000000000..35c4b3a869 --- /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 ATTRIBUTES = new ArrayList(); + 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 true if the attributes are valid, false 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 0000000000..292fa65923 --- /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 0000000000..9d456c8234 --- /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 0000000000..1f25766a29 --- /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 0000000000..c00bd8d227 --- /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 0000000000..269b3232dd --- /dev/null +++ b/integration/wildfly/wf8-subsystem/src/main/resources/schema/wildfly-keycloak_1_1.xsd @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the realm. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The name of the realm. + + + + + + + + + + + + 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 0000000000..0abb1245f8 --- /dev/null +++ b/integration/wildfly/wf8-subsystem/src/main/resources/subsystem-templates/keycloak-adapter.xml @@ -0,0 +1,7 @@ + + + + org.keycloak.keycloak-wf8-subsystem + + + 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 0000000000..1afeec4f1e --- /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 0000000000..93e9a59fcb --- /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 Kabir Khan + * @author Tomaz Cerar + * @author Marko Strukelj + */ +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 0000000000..2d12d88576 --- /dev/null +++ b/integration/wildfly/wf8-subsystem/src/test/resources/org/keycloak/subsystem/wf8/extension/keycloak-1.1.xml @@ -0,0 +1,24 @@ + + + master + web-console + true + + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB + + http://localhost:8080/auth + EXTERNAL + 0aa31d98-e0aa-404c-b6e0-e771dba1e798 + + + master + http-endpoint + true + + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC4siLKUew0WYxdtq6/rwk4Uj/4amGFFnE/yzIxQVU0PUqz3QBRVkUWpDj0K6ZnS5nzJV/y6DHLEy7hjZTdRDphyF1sq09aDOYnVpzu8o2sIlMM8q5RnUyEfIyUZqwo8pSZDJ90fS0s+IDUJNCSIrAKO3w1lqZDHL6E/YFHXyzkvQIDAQAB + + http://localhost:8080/auth + EXTERNAL + 2769a4a2-5be0-454f-838f-f33b7755b667 + + \ 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 0000000000..ca714a9b0b --- /dev/null +++ b/model/api/src/main/java/org/keycloak/mappers/MapperConfigValidationException.java @@ -0,0 +1,15 @@ +package org.keycloak.mappers; + +/** + * @author Marek Posolda + */ +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 386ce1307c..309036cb89 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 Marek Posolda */ public interface UserFederationMapperFactory extends ProviderFactory, 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 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 988d9ecfcc..77f825f00b 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 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; + } } diff --git a/pom.xml b/pom.xml index 5b846b3ebe..5ba74c69bf 100755 --- a/pom.xml +++ b/pom.xml @@ -1079,6 +1079,12 @@ ${project.version} zip + + org.keycloak + keycloak-wf8-modules + ${project.version} + zip + org.keycloak keycloak-server-overlay @@ -1272,7 +1278,9 @@ jboss-earlyaccess-repository - true + + !no-jboss-ea-repo + @@ -1303,6 +1311,7 @@ distribution distribution + testsuite/integration-arquillian @@ -1310,6 +1319,7 @@ docbook distribution + testsuite/integration-arquillian 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 6daf649a4c..d1545f2296 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/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index c2a4730bee..e2899fe0ea 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 0000000000..e5deb3d382 --- /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 Marek Posolda + */ +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 getMapperTypes() { + this.auth.requireView(); + KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); + Map types = new HashMap<>(); + List 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 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 getMappers() { + this.auth.requireView(); + List 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/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java similarity index 50% rename from services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java rename to services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java index 842fc5408d..a3bd867aad 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProvidersResource.java @@ -3,16 +3,14 @@ package org.keycloak.services.resources.admin; import org.jboss.logging.Logger; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; -import org.keycloak.constants.KerberosConstants; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.events.admin.OperationType; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; 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.provider.ProviderFactory; import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation; @@ -21,14 +19,11 @@ import org.keycloak.services.managers.UsersSyncManager; import org.keycloak.timer.TimerProvider; 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; @@ -43,8 +38,8 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class UserFederationResource { - protected static final Logger logger = Logger.getLogger(UserFederationResource.class); +public class UserFederationProvidersResource { + protected static final Logger logger = Logger.getLogger(UserFederationProvidersResource.class); protected RealmModel realm; @@ -58,7 +53,7 @@ public class UserFederationResource { @Context protected KeycloakSession session; - public UserFederationResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + public UserFederationProvidersResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent; @@ -88,7 +83,7 @@ public class UserFederationResource { } /** - * Get List of available provider factories + * Get factory with given ID * * @return */ @@ -130,77 +125,17 @@ public class UserFederationResource { UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName, rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync()); new UsersSyncManager().refreshPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId()); - checkKerberosCredential(model); + boolean kerberosCredsAdded = KeycloakModelUtils.checkKerberosCredential(realm, model); + if (kerberosCredsAdded) { + logger.info("Added 'kerberos' to required realm credentials"); + } + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(rep).success(); return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build(); } - /** - * Update a provider - * - * @param id - * @param rep - */ - @PUT - @Path("instances/{id}") - @Consumes(MediaType.APPLICATION_JSON) - public void updateProviderInstance(@PathParam("id") String id, UserFederationProviderRepresentation rep) { - auth.requireManage(); - String displayName = rep.getDisplayName(); - if (displayName != null && displayName.trim().equals("")) { - displayName = null; - } - UserFederationProviderModel model = new UserFederationProviderModel(id, 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()); - checkKerberosCredential(model); - - adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); - - } - - /** - * get a provider - * - * @param id - */ - @GET - @NoCache - @Path("instances/{id}") - @Produces(MediaType.APPLICATION_JSON) - public UserFederationProviderRepresentation getProviderInstance(@PathParam("id") String id) { - auth.requireView(); - for (UserFederationProviderModel model : realm.getUserFederationProviders()) { - if (model.getId().equals(id)) { - return ModelToRepresentation.toRepresentation(model); - } - } - throw new NotFoundException("could not find provider"); - } - - /** - * Delete a provider - * - * @param id - */ - @DELETE - @Path("instances/{id}") - public void deleteProviderInstance(@PathParam("id") String id) { - auth.requireManage(); - - UserFederationProviderRepresentation rep = getProviderInstance(id); - UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null, -1, -1, 0); - realm.removeUserFederationProvider(model); - new UsersSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), model); - - adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo).success(); - - } - - /** * list configured providers * @@ -220,53 +155,18 @@ public class UserFederationResource { return reps; } - /** - * trigger sync of users - * - * @return - */ - @POST - @Path("sync/{id}") - @NoCache - public UserFederationSyncResult syncUsers(@PathParam("id") String providerId, @QueryParam("action") String action) { - logger.debug("Syncing users"); - auth.requireManage(); + @Path("instances/{id}") + public UserFederationProviderResource getUserFederationInstance(@PathParam("id") String id) { + this.auth.requireView(); - for (UserFederationProviderModel model : realm.getUserFederationProviders()) { - if (model.getId().equals(providerId)) { - UsersSyncManager syncManager = new UsersSyncManager(); - UserFederationSyncResult syncResult = null; - if ("triggerFullSync".equals(action)) { - syncResult = syncManager.syncAllUsers(session.getKeycloakSessionFactory(), realm.getId(), model); - } else if ("triggerChangedUsersSync".equals(action)) { - syncResult = syncManager.syncChangedUsers(session.getKeycloakSessionFactory(), realm.getId(), model); - } - - adminEvent.operation(OperationType.ACTION).resourcePath(uriInfo).success(); - return syncResult; - } + UserFederationProviderModel model = KeycloakModelUtils.findUserFederationProviderById(id, realm); + if (model == null) { + throw new NotFoundException("Could not find federation provider with id: " + id); } - throw new NotFoundException("could not find provider"); - } - - // Automatically add "kerberos" to required realm credentials if it's supported by saved provider - private void checkKerberosCredential(UserFederationProviderModel model) { - String allowKerberosCfg = model.getConfig().get(KerberosConstants.ALLOW_KERBEROS_AUTHENTICATION); - if (Boolean.valueOf(allowKerberosCfg)) { - boolean found = false; - List currentCreds = realm.getRequiredCredentials(); - for (RequiredCredentialModel cred : currentCreds) { - if (cred.getType().equals(UserCredentialModel.KERBEROS)) { - found = true; - } - } - - if (!found) { - realm.addRequiredCredential(UserCredentialModel.KERBEROS); - logger.info("Added 'kerberos' to required realm credentials"); - } - } + UserFederationProviderResource instanceResource = new UserFederationProviderResource(session, realm, this.auth, model, adminEvent); + ResteasyProviderFactory.getInstance().injectProperties(instanceResource); + return instanceResource; } } diff --git a/testsuite/integration-arquillian/README.md b/testsuite/integration-arquillian/README.md new file mode 100644 index 0000000000..a81701d1b3 --- /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/pom.xml b/testsuite/integration-arquillian/pom.xml new file mode 100644 index 0000000000..2435a1a57e --- /dev/null +++ b/testsuite/integration-arquillian/pom.xml @@ -0,0 +1,182 @@ + + + + keycloak-testsuite-pom + org.keycloak + 1.3.0.Final-SNAPSHOT + ../pom.xml + + 4.0.0 + + arquillian-integration + KeyCloak Admin UI TestSuite + + + phantomjs + + 1.1.5.Final + 2.45.0 + 1.3.1.Final + 1.1.4.Final + 2.0.3.Final + 8.1.0.Final + + + true + + + + + + org.jboss.arquillian.selenium + selenium-bom + ${selenium.version} + pom + import + + + org.jboss.arquillian + arquillian-bom + ${arquillian-core.version} + pom + import + + + org.jboss.arquillian.extension + arquillian-drone-bom + ${arquillian-drone.version} + pom + import + + + org.wildfly + wildfly-arquillian-container-remote + ${arquillian-wildfly-container.version} + + + org.wildfly + wildfly-arquillian-container-managed + ${arquillian-wildfly-container.version} + + + + + + + junit + junit + test + + + org.jboss.arquillian.junit + arquillian-junit-container + test + + + org.jboss.arquillian.graphene + graphene-webdriver + ${arquillian-graphene.version} + pom + test + + + org.jboss.arquillian.extension + arquillian-phantom-driver + ${arquillian-phantomjs.version} + test + + + + + + wildfly-8-remote + + + org.wildfly + wildfly-arquillian-container-remote + test + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + false + wildfly-8-remote + ${browser} + ${first.login} + + + + + + + + + wildfly-8-managed + + true + + + + org.wildfly + wildfly-arquillian-container-managed + test + + + + ${project.build.directory}/install + ${install.directory}/keycloak-${project.version} + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.10 + + + unpack + process-test-resources + + unpack + + + + + org.keycloak + keycloak-server-dist + ${project.version} + zip + false + + + ${install.directory} + false + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + false + wildfly-8-managed + ${browser} + ${jbossHome} + + + + + + + + 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 0000000000..aa1be23386 --- /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

    + */ +public abstract class AbstractKeyCloakTest

    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 0000000000..b05331e7d1 --- /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 0000000000..bae764fb4c --- /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 0000000000..33a84afa67 --- /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 0000000000..2f5365840d --- /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 menuList; + + @FindBy(css = ".dropdown-toggle") + private List 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 0000000000..4175c45522 --- /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 0000000000..3fc3a9070d --- /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 0000000000..7711585afe --- /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 0000000000..f2a2ef019b --- /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 0000000000..5172f8fdf0 --- /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 0000000000..6d2c65191f --- /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 0000000000..869eec2be2 --- /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 0000000000..efe617850f --- /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 0000000000..cb13d68406 --- /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 0000000000..0578449e3c --- /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 0000000000..e4be9f581b --- /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 0000000000..bf6a396a08 --- /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 0000000000..8251ab242f --- /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 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/LoginPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/LoginPage.java new file mode 100644 index 0000000000..06ef63eddf --- /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 0000000000..1c3ad33a65 --- /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/account/AccountPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/account/AccountPage.java new file mode 100644 index 0000000000..0c8ab504d0 --- /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 0000000000..b0a3e9d82b --- /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/session/SessionsPage.java b/testsuite/integration-arquillian/src/test/java/org/keycloak/testsuite/ui/page/session/SessionsPage.java new file mode 100644 index 0000000000..d714a6404a --- /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 0000000000..890c7e5528 --- /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 clients = getAllRows(); + if (clients.isEmpty()) { + return null; + + } else { + assertEquals(1, clients.size()); + return clients.get(0); + } + } + + private List getAllRows() { + List rows = new ArrayList(); + List allRows = dataTable.findElements(cssSelector("tbody tr")); + if (allRows.size() > 1) { + for (WebElement rowElement : allRows) { + if (rowElement.isDisplayed()) { + Client client = new Client(); + List 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 0000000000..a0455f5f72 --- /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 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 0000000000..d76ede60f4 --- /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 roleList = new ArrayList(); + 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 0000000000..bfab15d6ba --- /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 0000000000..84049dc188 --- /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 0000000000..1ddaca5e5c --- /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 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 getAllRows() { + List rows = new ArrayList(); + for (WebElement rowElement : dataTable.findElements(cssSelector("tbody tr"))) { + Role role = new Role(); + List 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 0000000000..13d6a073a7 --- /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 0000000000..ef38396b08 --- /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 list = getAllRows(); + for(Provider p : list) { + if(p.providerName == provider) { + return p; + } + } + return null; + } + + private List getAllRows() { + List rows = new ArrayList(); + for (WebElement rowElement : providersTable.findElements(tagName("tr"))) { + Provider provider = new Provider(); + List 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 0000000000..d311d4daa6 --- /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 0000000000..fc8d433533 --- /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 0000000000..18450b413c --- /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 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 getAllRows() { + List users = new ArrayList(); + List rows = dataTable.findElements(cssSelector("tbody tr")); + if (rows.size() > 1) { + for (WebElement rowElement : rows) { + if (rowElement.isDisplayed()) { + User user = new User(); + List 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 0000000000..537fee30f6 --- /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 { + + @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 0000000000..8e0cb19a54 --- /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 { + + @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 0000000000..7bcd681d7b --- /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 { + + @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 0000000000..41c04c0733 --- /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 { + + @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 0000000000..ba1c2e6c46 --- /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 { + + 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 0000000000..f016db14d0 --- /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 { + + @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 0000000000..b748bd6565 --- /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{ + + @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 0000000000..71ecc4eb4c --- /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 { + + @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 0000000000..c4686ff553 --- /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 { + + @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 0000000000..5694c51362 --- /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 { + + @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 0000000000..834ba25263 --- /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 { + + @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 0000000000..3bdde85c6f --- /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 0000000000..15a4ee5c36 --- /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 0000000000..ffde485f85 --- /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 0000000000..77a6b5fe9e --- /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 0000000000..91393a95ef --- /dev/null +++ b/testsuite/integration-arquillian/src/test/resources/arquillian.xml @@ -0,0 +1,23 @@ + + + + + + REMOTE + + + + + + ${jbossHome} + standalone.xml + + + + + ${browser} + + 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 ddda4ff856..d3dd9ab373 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 68abb4aba3..ed945026aa 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 4d20bc8eec..276698de93 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));