From 8ad3c3fb030c68b1bdbdcc6106be28667b89f025 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Fri, 29 Jul 2016 11:16:05 +0200 Subject: [PATCH 01/49] Package Fuse adapter as overlay zip --- .../adapters/fuse-adapter-zip/assembly.xml | 36 ++++ .../adapters/fuse-adapter-zip/pom.xml | 167 ++++++++++++++++++ distribution/adapters/pom.xml | 1 + 3 files changed, 204 insertions(+) create mode 100644 distribution/adapters/fuse-adapter-zip/assembly.xml create mode 100644 distribution/adapters/fuse-adapter-zip/pom.xml diff --git a/distribution/adapters/fuse-adapter-zip/assembly.xml b/distribution/adapters/fuse-adapter-zip/assembly.xml new file mode 100644 index 0000000000..10667933f7 --- /dev/null +++ b/distribution/adapters/fuse-adapter-zip/assembly.xml @@ -0,0 +1,36 @@ + + + + fuse-adapter-dist + + + zip + tar.gz + + false + + + + ${project.build.directory}/system + + */** + + system + + + \ No newline at end of file diff --git a/distribution/adapters/fuse-adapter-zip/pom.xml b/distribution/adapters/fuse-adapter-zip/pom.xml new file mode 100644 index 0000000000..86128fc00c --- /dev/null +++ b/distribution/adapters/fuse-adapter-zip/pom.xml @@ -0,0 +1,167 @@ + + + + 4.0.0 + + keycloak-parent + org.keycloak + 2.1.0-SNAPSHOT + ../../../pom.xml + + + keycloak-fuse-adapter-dist + pom + Keycloak Fuse Adapter Distro + + + + + org.keycloak + keycloak-osgi-features + ${project.version} + + + org.keycloak + keycloak-osgi-thirdparty + ${project.version} + + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-base + + + com.fasterxml.jackson.jaxrs + jackson-jaxrs-json-provider + + + org.jboss.logging + jboss-logging + + + org.keycloak + keycloak-common + + + org.keycloak + keycloak-core + + + org.keycloak + keycloak-adapter-spi + + + org.keycloak + keycloak-adapter-core + + + org.keycloak + keycloak-osgi-adapter + + + org.keycloak + keycloak-jetty-adapter-spi + + + org.keycloak + keycloak-jetty-core + + + org.keycloak + keycloak-jetty81-adapter + + + org.keycloak + keycloak-osgi-jaas + ${project.version} + + + org.keycloak + keycloak-jetty92-adapter + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + prepare-package + + copy-dependencies + + + ${project.build.directory}/system + false + false + true + true + + + + + + maven-assembly-plugin + + + assemble + package + + single + + + + assembly.xml + + target + target/assembly/work + false + + + + + + + \ No newline at end of file diff --git a/distribution/adapters/pom.xml b/distribution/adapters/pom.xml index 5dcd20d647..b815249f64 100755 --- a/distribution/adapters/pom.xml +++ b/distribution/adapters/pom.xml @@ -32,6 +32,7 @@ as7-eap6-adapter + fuse-adapter-zip jetty81-adapter-zip jetty91-adapter-zip jetty92-adapter-zip From 4d8e19eb7c702a7032c4780a1f1aeea9e9e2e888 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 10 Aug 2016 17:28:38 -0300 Subject: [PATCH 02/49] Removal of never used classes from the changeset package --- .../keycloak/storage/changeset/UserData.java | 387 ------------------ .../storage/changeset/UserDataAdapter.java | 340 --------------- 2 files changed, 727 deletions(-) delete mode 100755 server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java delete mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java deleted file mode 100755 index 4b2813a87a..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * 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.storage.changeset; - -import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.models.UserCredentialValueModel; -import org.keycloak.models.entities.AbstractIdentifiableEntity; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -/** - * @author Marek Posolda - */ -public class UserData { - - private String id; - private boolean idChanged; - private String username; - private boolean usernameChanged; - private Long createdTimestamp; - private boolean createdTimestampChanged; - private String firstName; - private boolean firstNameChanged; - private String lastName; - private boolean lastNameChanged; - private String email; - private boolean emailChanged; - private boolean emailVerified; - private boolean emailVerifiedChanged; - private boolean totp; - private boolean totpChanged; - private boolean enabled; - private boolean enabledChanged; - - private Set roleIds = new HashSet<>(); - private boolean rolesChanged; - private Set groupIds = new HashSet<>(); - private boolean groupsChanged; - - private MultivaluedHashMap attributes = new MultivaluedHashMap<>(); - private boolean attributesChanged; - private Set requiredActions = new HashSet<>(); - private boolean requiredActionsChanged; - private List credentials = new LinkedList<>(); - private boolean credentialsChanged; - - public void rememberState() { - original = new UserData(); - original.id = id; - original.username = username; - original.createdTimestamp = createdTimestamp; - original.firstName = firstName; - original.lastName = lastName; - original.email = email; - original.emailVerified = emailVerified; - original.totp = totp; - original.enabled = enabled; - original.attributes.putAll(attributes); - original.requiredActions.addAll(requiredActions); - original.credentials.addAll(credentials); - } - - private UserData original = null; - - public void clearChangeFlags() { - original = null; - idChanged = false; - usernameChanged = false; - createdTimestampChanged = false; - firstNameChanged = false; - lastNameChanged = false; - emailChanged = false; - emailVerifiedChanged = false; - totpChanged = false; - enabledChanged = false; - rolesChanged = false; - groupsChanged = false; - attributesChanged = false; - requiredActionsChanged = false; - credentialsChanged = false; - } - - public boolean isChanged() { - return !idChanged - && !usernameChanged - && !createdTimestampChanged - && !firstNameChanged - && !lastNameChanged - && !emailChanged - && !emailVerifiedChanged - && !totpChanged - && !enabledChanged - && !rolesChanged - && !groupsChanged - && !attributesChanged - && !requiredActionsChanged - && !credentialsChanged; - } - - public boolean isIdChanged() { - return idChanged; - } - - public boolean isUsernameChanged() { - return usernameChanged; - } - - public boolean isCreatedTimestampChanged() { - return createdTimestampChanged; - } - - public boolean isFirstNameChanged() { - return firstNameChanged; - } - - public boolean isLastNameChanged() { - return lastNameChanged; - } - - public boolean isEmailChanged() { - return emailChanged; - } - - public boolean isEmailVerifiedChanged() { - return emailVerifiedChanged; - } - - public boolean isTotpChanged() { - return totpChanged; - } - - public boolean isEnabledChanged() { - return enabledChanged; - } - - public boolean isRolesChanged() { - return rolesChanged; - } - - public boolean isGroupsChanged() { - return groupsChanged; - } - - public boolean isAttributesChanged() { - return attributesChanged; - } - - public boolean isRequiredActionsChanged() { - return requiredActionsChanged; - } - - public boolean isCredentialsChanged() { - return credentialsChanged; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - idChanged = true; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - usernameChanged = true; - } - - public Long getCreatedTimestamp() { - return createdTimestamp; - } - - public void setCreatedTimestamp(Long timestamp) { - this.createdTimestamp = timestamp; - createdTimestampChanged = true; - } - - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - firstNameChanged = true; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - lastNameChanged = true; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - emailChanged = true; - } - - public boolean isEmailVerified() { - return emailVerified; - } - - public void setEmailVerified(boolean emailVerified) { - this.emailVerified = emailVerified; - emailVerifiedChanged = true; - } - - public boolean isTotp() { - return totp; - } - - public void setTotp(boolean totp) { - this.totp = totp; - totpChanged = true; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - enabledChanged = true; - } - - public Set getRoleMappings() { - return Collections.unmodifiableSet(roleIds); - } - - public void grantRole(String roleId) { - if (roleIds.contains(roleId)) return; - roleIds.add(roleId); - rolesChanged = true; - } - - public void deleteRoleMapping(String roleId) { - if (!roleIds.contains(roleId)) return; - roleIds.remove(roleId); - rolesChanged = true; - } - - public MultivaluedHashMap getAttributes() { - return attributes; - } - - public void setSingleAttribute(String name, String value) { - attributes.putSingle(name, value); - attributesChanged = true; - - } - public void setAttribute(String name, List values) { - attributes.put(name, values); - attributesChanged = true; - } - public void removeAttribute(String name) { - attributes.remove(name); - attributesChanged = true; - } - - - - public Set getRequiredActions() { - return Collections.unmodifiableSet(requiredActions); - } - public void addRequiredAction(String action) { - if (requiredActions.contains(action)) return; - requiredActions.add(action); - requiredActionsChanged = true; - } - public void removeRequiredAction(String action) { - if (!requiredActions.contains(action)) return; - requiredActions.remove(action); - requiredActionsChanged = true; - } - - public List getCredentials() { - return Collections.unmodifiableList(credentials); - } - - public void removeCredentialType(String type) { - Iterator it = credentials.iterator(); - while (it.hasNext()) { - if (it.next().getType().equals(type)) { - it.remove(); - credentialsChanged = true; - } - } - - } - - public void removeCredentialDevice(String type, String device) { - Iterator it = credentials.iterator(); - while (it.hasNext()) { - UserCredentialValueModel next = it.next(); - if (next.getType().equals(type) && next.getDevice().equals(device)) { - it.remove(); - credentialsChanged = true; - } - } - - } - - public void setCredential(UserCredentialValueModel cred) { - removeCredentialType(cred.getType()); - addCredential(cred); - } - public void addCredential(UserCredentialValueModel cred) { - credentials.add(cred); - credentialsChanged = true; - } - - public Set getGroupIds() { - return Collections.unmodifiableSet(groupIds); - } - - public void joinGroup(String groupId) { - if (groupIds.contains(groupId)) return; - groupIds.add(groupId); - groupsChanged = true; - } - - public void leaveGroup(String groupId) { - if (!groupIds.contains(groupId)) return; - groupIds.remove(groupId); - groupsChanged = true; - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - - if (this.id == null) return false; - - if (o == null || getClass() != o.getClass()) return false; - - AbstractIdentifiableEntity that = (AbstractIdentifiableEntity) o; - - if (!getId().equals(that.getId())) return false; - - return true; - - } - - @Override - public int hashCode() { - return id!=null ? id.hashCode() : super.hashCode(); - } - - @Override - public String toString() { - return String.format("%s [ id=%s ]", getClass().getSimpleName(), getId()); - } - -} - diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java deleted file mode 100644 index 6ec78f25b6..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * 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.storage.changeset; - -import org.keycloak.common.util.Time; -import org.keycloak.hash.PasswordHashManager; -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.OTPPolicy; -import org.keycloak.models.PasswordPolicy; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleContainerModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserCredentialValueModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * - * - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class UserDataAdapter implements UserModel { - protected UserData userData; - protected RealmModel realm; - protected KeycloakSession session; - protected Set managedCredentialTypes; - protected List updatedManagedCredentials = new LinkedList<>(); - - public UserDataAdapter(KeycloakSession session, RealmModel realm, UserData userData) { - this.session = session; - this.realm = realm; - this.userData = userData; - this.userData.rememberState(); - } - - @Override - public String getId() { - return userData.getId(); - } - - @Override - public String getUsername() { - return userData.getUsername(); - } - - @Override - public void setUsername(String username) { - userData.setUsername(username); - - } - - @Override - public Long getCreatedTimestamp() { - return userData.getCreatedTimestamp(); - } - - @Override - public void setCreatedTimestamp(Long timestamp) { - userData.setCreatedTimestamp(timestamp); - - } - - @Override - public boolean isEnabled() { - return userData.isEnabled(); - } - - @Override - public boolean isOtpEnabled() { - return userData.isTotp(); - } - - @Override - public void setEnabled(boolean enabled) { - userData.setEnabled(enabled); - - } - - @Override - public void setSingleAttribute(String name, String value) { - userData.setSingleAttribute(name, value); - - } - - @Override - public void setAttribute(String name, List values) { - userData.setAttribute(name, values); - - } - - @Override - public void removeAttribute(String name) { - userData.removeAttribute(name); - - } - - @Override - public String getFirstAttribute(String name) { - return userData.getAttributes().getFirst(name); - } - - @Override - public List getAttribute(String name) { - return userData.getAttributes().get(name); - } - - @Override - public Map> getAttributes() { - return userData.getAttributes(); - } - - @Override - public Set getRequiredActions() { - return userData.getRequiredActions(); - } - - @Override - public void addRequiredAction(String action) { - userData.addRequiredAction(action); - - } - - @Override - public void removeRequiredAction(String action) { - userData.removeRequiredAction(action); - - } - - @Override - public void addRequiredAction(RequiredAction action) { - userData.addRequiredAction(action.name()); - - } - - @Override - public void removeRequiredAction(RequiredAction action) { - userData.removeRequiredAction(action.name()); - - } - - @Override - public String getFirstName() { - return userData.getFirstName(); - } - - @Override - public void setFirstName(String firstName) { - userData.setFirstName(firstName); - - } - - @Override - public String getLastName() { - return userData.getLastName(); - } - - @Override - public void setLastName(String lastName) { - userData.setLastName(lastName); - - } - - @Override - public String getEmail() { - return userData.getEmail(); - } - - @Override - public void setEmail(String email) { - userData.setEmail(email); - - } - - @Override - public boolean isEmailVerified() { - return userData.isEmailVerified(); - } - - @Override - public void setEmailVerified(boolean verified) { - userData.setEmailVerified(verified); - - } - - @Override - public void setOtpEnabled(boolean totp) { - userData.setTotp(totp); - - } - - @Override - public void updateCredential(UserCredentialModel cred) { - - } - - @Override - public List getCredentialsDirectly() { - return null; - } - - @Override - public void updateCredentialDirectly(UserCredentialValueModel cred) { - - } - - @Override - public Set getGroups() { - Set groups = userData.getGroupIds(); - Set set = new HashSet<>(); - for (String id : groups) { - GroupModel group = realm.getGroupById(id); - if (group != null) set.add(group); - } - return set; - } - - @Override - public void joinGroup(GroupModel group) { - userData.joinGroup(group.getId()); - - } - - @Override - public void leaveGroup(GroupModel group) { - userData.leaveGroup(group.getId()); - - } - - @Override - public boolean isMemberOf(GroupModel group) { - Set roles = getGroups(); - return KeycloakModelUtils.isMember(roles, group); - } - - @Override - public String getFederationLink() { - return null; - } - - @Override - public void setFederationLink(String link) { - - } - - @Override - public String getServiceAccountClientLink() { - return null; - } - - @Override - public void setServiceAccountClientLink(String clientInternalId) { - - } - - @Override - public Set getRealmRoleMappings() { - Set roleMappings = getRoleMappings(); - - Set realmRoles = new HashSet(); - for (RoleModel role : roleMappings) { - RoleContainerModel container = role.getContainer(); - if (container instanceof RealmModel) { - realmRoles.add(role); - } - } - return realmRoles; - } - - @Override - public Set getClientRoleMappings(ClientModel app) { - Set roleMappings = getRoleMappings(); - - Set roles = new HashSet(); - for (RoleModel role : roleMappings) { - RoleContainerModel container = role.getContainer(); - if (container instanceof ClientModel) { - ClientModel appModel = (ClientModel)container; - if (appModel.getId().equals(app.getId())) { - roles.add(role); - } - } - } - return roles; - } - - @Override - public boolean hasRole(RoleModel role) { - Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); - } - - @Override - public void grantRole(RoleModel role) { - userData.grantRole(role.getId()); - - } - - @Override - public Set getRoleMappings() { - Set roles = userData.getRoleMappings(); - Set set = new HashSet<>(); - for (String id : roles) { - RoleModel role = realm.getRoleById(id); - if (role != null) set.add(role); - } - return set; - } - - @Override - public void deleteRoleMapping(RoleModel role) { - userData.deleteRoleMapping(role.getId()); - } -} From de888fc8a106a819c1cd7875b95511bb2dbe0efa Mon Sep 17 00:00:00 2001 From: fkiss Date: Thu, 11 Aug 2016 10:52:29 +0200 Subject: [PATCH 03/49] KEYCLOAK-3351 TrustStoreEmailTest fix for ibmjdk --- .../test/java/org/keycloak/testsuite/util/SslMailServer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SslMailServer.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SslMailServer.java index 0088e42070..0f87d49ab7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SslMailServer.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/SslMailServer.java @@ -91,7 +91,7 @@ public class SslMailServer { ksKeys.load(keyStoreIS, keyStorePassphrase); // KeyManager decides which key material to use. - KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ksKeys, keyStorePassphrase); // Trust store for client authentication. @@ -101,7 +101,7 @@ public class SslMailServer { ksTrust.load(trustStoreIS, trustStorePassphrase); // TrustManager decides which certificate authorities to use. - TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); tmf.init(ksTrust); final SSLContext sslContext = SSLContext.getInstance("TLS"); From b7f2e0b5ff99d9a5d27df5bb78f5fca3c75170bf Mon Sep 17 00:00:00 2001 From: Vaclav Muzikar Date: Fri, 12 Aug 2016 14:02:17 +0200 Subject: [PATCH 04/49] KEYCLOAK-3429 Fix behaviour of redirect_uri parameter with query components --- .../protocol/oidc/utils/RedirectUtils.java | 10 +++++--- .../testsuite/oauth/OAuthRedirectUriTest.java | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java index 441cd57531..b7d9ad4246 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/RedirectUtils.java @@ -77,7 +77,7 @@ public class RedirectUtils { } else { redirectUri = lowerCaseHostname(redirectUri); - String r = redirectUri.indexOf('?') != -1 ? redirectUri.substring(0, redirectUri.indexOf('?')) : redirectUri; + String r = redirectUri; Set resolveValidRedirects = resolveValidRedirects(uriInfo, rootUrl, validRedirects); boolean valid = matchesRedirects(resolveValidRedirects, r); @@ -134,15 +134,17 @@ public class RedirectUtils { private static boolean matchesRedirects(Set validRedirects, String redirect) { for (String validRedirect : validRedirects) { - if (validRedirect.endsWith("*")) { + if (validRedirect.endsWith("*") && !validRedirect.contains("?")) { + // strip off the query component - we don't check them when wildcards are effective + String r = redirect.contains("?") ? redirect.substring(0, redirect.indexOf("?")) : redirect; // strip off * int length = validRedirect.length() - 1; validRedirect = validRedirect.substring(0, length); - if (redirect.startsWith(validRedirect)) return true; + if (r.startsWith(validRedirect)) return true; // strip off trailing '/' if (length - 1 > 0 && validRedirect.charAt(length - 1) == '/') length--; validRedirect = validRedirect.substring(0, length); - if (validRedirect.equals(redirect)) return true; + if (validRedirect.equals(r)) return true; } else if (validRedirect.equals(redirect)) return true; } return false; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java index f80e789ef7..520f910e77 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java @@ -97,6 +97,11 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest { .secret("password"); realm.client(installedApp6); + ClientBuilder installedApp7 = ClientBuilder.create().id("test-query-component").name("test-query-component") + .redirectUris("http://localhost?foo=bar", "http://localhost?foo=bar*") + .secret("password"); + realm.client(installedApp7); + testRealms.add(realm.build()); } @@ -185,6 +190,26 @@ public class OAuthRedirectUriTest extends AbstractKeycloakTest { Assert.assertTrue(url.getQuery().contains("code=")); } + @Test + public void testQueryComponents() throws IOException { + // KEYCLOAK-3420 + oauth.clientId("test-query-component"); + checkRedirectUri("http://localhost?foo=bar", true); + checkRedirectUri("http://localhost?foo=bara", false); + checkRedirectUri("http://localhost?foo=bar/", false); + checkRedirectUri("http://localhost?foo2=bar2&foo=bar", false); + checkRedirectUri("http://localhost?foo=b", false); + checkRedirectUri("http://localhost?foo", false); + checkRedirectUri("http://localhost?foo=bar&bar=foo", false); + checkRedirectUri("http://localhost?foo&bar=foo", false); + checkRedirectUri("http://localhost?foo&bar", false); + checkRedirectUri("http://localhost", false); + + // KEYCLOAK-3418 + oauth.clientId("test-installed"); + checkRedirectUri("http://localhost?foo=bar", false); + } + @Test public void testWildcard() throws IOException { oauth.clientId("test-wildcard"); From 285a99d90318fc9bb0dc80f236cffb3c6a59b85e Mon Sep 17 00:00:00 2001 From: mhajas Date: Tue, 2 Aug 2016 13:14:47 +0200 Subject: [PATCH 05/49] Migrate SAML adapter tests --- .../page/BadAssertionSalesPostSig.java | 39 ++ .../adapter/page/EmployeeServlet.java | 39 ++ .../testsuite/adapter/page/InputPortal.java | 6 +- .../adapter/page/MissingAssertionSig.java | 39 ++ .../testsuite/adapter/page/SAMLServlet.java | 4 +- .../adapter/page/SalesPost2Servlet.java | 39 ++ .../SalesPostAssertionAndResponseSig.java | 39 ++ .../adapter/servlet/InputServlet.java | 24 ++ .../adapter/servlet/SamlSPFacade.java | 69 +++ .../adapter/servlet/SendUsernameServlet.java | 91 +++- .../DeploymentArchiveProcessor.java | 73 ++-- .../KeycloakDependenciesResolver.java | 39 ++ .../arquillian/SAMLFilterDependency.java | 87 ---- .../org/keycloak/testsuite/util/IOUtil.java | 88 +++- .../adapter/AbstractAdapterTest.java | 16 + .../adapter/AbstractServletsAdapterTest.java | 11 +- .../AbstractSAMLFilterServletAdapterTest.java | 21 +- .../AbstractSAMLServletsAdapterTest.java | 406 +++++++++++++++--- .../WEB-INF/keycloak-saml.xml | 62 +++ .../WEB-INF/keystore.jks | Bin 0 -> 1715 bytes .../employee/WEB-INF/keycloak-saml.xml | 42 ++ .../keycloak-saml/employee/WEB-INF/web.xml | 54 +++ .../input-portal/WEB-INF/keycloak-saml.xml | 41 ++ .../input-portal/WEB-INF/web.xml | 54 +++ .../WEB-INF/keycloak-saml.xml | 60 +++ .../WEB-INF/keystore.jks | Bin 0 -> 1705 bytes .../WEB-INF/keycloak-saml.xml | 60 +++ .../WEB-INF/keystore.jks | Bin 0 -> 1705 bytes .../sales-post2/WEB-INF/keycloak-saml.xml | 41 ++ .../adapter-test/keycloak-saml/testsaml.json | 95 ++++ .../adapter-test/keycloak-saml/web.xml | 4 + .../adapter/EAP6SAMLFilterAdapterTest.java | 2 +- .../tests/other/adapters/pom.xml | 12 + .../integration-arquillian/tests/pom.xml | 4 + 34 files changed, 1479 insertions(+), 182 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadAssertionSalesPostSig.java create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeServlet.java create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/MissingAssertionSig.java create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPost2Servlet.java create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAssertionAndResponseSig.java create mode 100755 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SamlSPFacade.java create mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakDependenciesResolver.java delete mode 100644 testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SAMLFilterDependency.java create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keycloak-saml.xml create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keystore.jks create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/keycloak-saml.xml create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/web.xml create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/keycloak-saml.xml create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/web.xml create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keystore.jks create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keystore.jks create mode 100755 testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post2/WEB-INF/keycloak-saml.xml diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadAssertionSalesPostSig.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadAssertionSalesPostSig.java new file mode 100644 index 0000000000..8d10b22a04 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/BadAssertionSalesPostSig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.adapter.page; + +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.test.api.ArquillianResource; + +import java.net.URL; + +/** + * @author mhajas + */ +public class BadAssertionSalesPostSig extends SAMLServlet { + public static final String DEPLOYMENT_NAME = "bad-assertion-sales-post-sig"; + + @ArquillianResource + @OperateOnDeployment(DEPLOYMENT_NAME) + private URL url; + + @Override + public URL getInjectedUrl() { + return url; + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeServlet.java new file mode 100644 index 0000000000..c8a0d662a4 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/EmployeeServlet.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.adapter.page; + +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.test.api.ArquillianResource; + +import java.net.URL; + +/** + * @author mhajas + */ +public class EmployeeServlet extends SAMLServlet { + public static final String DEPLOYMENT_NAME = "employee"; + + @ArquillianResource + @OperateOnDeployment(DEPLOYMENT_NAME) + private URL url; + + @Override + public URL getInjectedUrl() { + return url; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/InputPortal.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/InputPortal.java index 6e66e2c32d..7edc5c5bc8 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/InputPortal.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/InputPortal.java @@ -17,18 +17,18 @@ package org.keycloak.testsuite.adapter.page; -import java.net.URL; import org.jboss.arquillian.container.test.api.OperateOnDeployment; import org.jboss.arquillian.test.api.ArquillianResource; -import org.keycloak.testsuite.page.AbstractPageWithInjectedUrl; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; +import java.net.URL; + /** * * @author tkyjovsk */ -public class InputPortal extends AbstractPageWithInjectedUrl { +public class InputPortal extends SAMLServlet { public static final String DEPLOYMENT_NAME = "input-portal"; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/MissingAssertionSig.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/MissingAssertionSig.java new file mode 100644 index 0000000000..78ff38c10a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/MissingAssertionSig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.adapter.page; + +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.test.api.ArquillianResource; + +import java.net.URL; + +/** + * @author mhajas + */ +public class MissingAssertionSig extends SAMLServlet { + public static final String DEPLOYMENT_NAME = "missing-assertion-sig"; + + @ArquillianResource + @OperateOnDeployment(DEPLOYMENT_NAME) + private URL url; + + @Override + public URL getInjectedUrl() { + return url; + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLServlet.java index fe856dc14a..cc4a419725 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLServlet.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SAMLServlet.java @@ -40,8 +40,8 @@ public abstract class SAMLServlet extends AbstractPageWithInjectedUrl { } } - public void checkRolesEndPoint() { - driver.navigate().to(getUriBuilder().build().toASCIIString() + "/checkRoles"); + public void checkRolesEndPoint(boolean value) { + driver.navigate().to(getUriBuilder().build().toASCIIString() + "/" + (value ? "" : "un") + "checkRoles"); pause(300); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPost2Servlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPost2Servlet.java new file mode 100644 index 0000000000..d821d868d6 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPost2Servlet.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.adapter.page; + +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.test.api.ArquillianResource; + +import java.net.URL; + +/** + * @author mhajas + */ +public class SalesPost2Servlet extends SAMLServlet { + public static final String DEPLOYMENT_NAME = "sales-post2"; + + @ArquillianResource + @OperateOnDeployment(DEPLOYMENT_NAME) + private URL url; + + @Override + public URL getInjectedUrl() { + return url; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAssertionAndResponseSig.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAssertionAndResponseSig.java new file mode 100644 index 0000000000..a4522b4b2d --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/page/SalesPostAssertionAndResponseSig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.adapter.page; + +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.test.api.ArquillianResource; + +import java.net.URL; + +/** + * @author mhajas + */ +public class SalesPostAssertionAndResponseSig extends SAMLServlet { + public static final String DEPLOYMENT_NAME = "sales-post-assertion-and-response-sig"; + + @ArquillianResource + @OperateOnDeployment(DEPLOYMENT_NAME) + private URL url; + + @Override + public URL getInjectedUrl() { + return url; + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/InputServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/InputServlet.java index a2038bf698..5fb04e9435 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/InputServlet.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/InputServlet.java @@ -17,6 +17,8 @@ package org.keycloak.testsuite.adapter.servlet; +import org.junit.Assert; + import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -31,6 +33,8 @@ import java.io.PrintWriter; @WebServlet("/input-portal") public class InputServlet extends HttpServlet { + private static final String FORM_URLENCODED = "application/x-www-form-urlencoded"; + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String appBase; @@ -41,6 +45,16 @@ public class InputServlet extends HttpServlet { } String actionUrl = appBase + "/input-portal/secured/post"; + if (req.getRequestURI().endsWith("insecure")) { + if (System.getProperty("insecure.user.principal.unsupported") == null) Assert.assertNotNull(req.getUserPrincipal()); + resp.setContentType("text/html"); + PrintWriter pw = resp.getWriter(); + pw.printf("Input Servlet%s\n", "Insecure Page"); + if (req.getUserPrincipal() != null) pw.printf("UserPrincipal: " + req.getUserPrincipal().getName()); + pw.print(""); + pw.flush(); + return; + } resp.setContentType("text/html"); PrintWriter pw = resp.getWriter(); @@ -56,6 +70,16 @@ public class InputServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (!FORM_URLENCODED.equals(req.getContentType())) { + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + PrintWriter pw = resp.getWriter(); + resp.setContentType("text/plain"); + pw.printf("Expecting content type " + FORM_URLENCODED + + ", received " + req.getContentType() + " instead"); + pw.flush(); + return; + } + resp.setContentType("text/plain"); PrintWriter pw = resp.getWriter(); pw.printf("parameter="+req.getParameter("parameter")); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SamlSPFacade.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SamlSPFacade.java new file mode 100755 index 0000000000..21f07b3316 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SamlSPFacade.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.adapter.servlet; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.UriBuilder; +import java.io.IOException; +import java.io.PrintWriter; + +/** +* @author Bill Burke +* @version $Revision: 1 $ +*/ +public class SamlSPFacade extends HttpServlet { + public static String samlResponse; + public static String RELAY_STATE = "http://test.com/foo/bar"; + public static String sentRelayState; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + handler(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + handler(req, resp); + } + + private void handler(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + System.out.println("In SamlSPFacade Servlet handler()"); + if (req.getParameterMap().isEmpty()) { + System.out.println("ParameterMap is empty, redirecting to keycloak server "); + resp.setStatus(302); + // Redirect + // UriBuilder builder = UriBuilder.fromUri("http://localhost:8081/auth/realms/demo/protocol/saml?SAMLRequest=jVLRTsIwFP2Vpe%2BjG4wxG0YyWYxL0BBAH3wx3XYnTbp29nYof%2B8YEvEBNOlD03vOveec2ynyWjYsae1WreC9BbTOZy0Vsr4Qk9YopjkKZIrXgMwWbJ08LNhw4LHGaKsLLcmRch3MEcFYoRVxktN1rhW2NZg1mJ0o4Gm1iMnW2oZRKnXB5VajZZEX%2BRTqRuo9ACVO2mkUih%2F4l9C8s0MNcFkjLaHW9KSUHlwR506bAnrPMam4RCBOlsYkS1%2BD3MvLcDJxAx9KN4jCkXszrG5cP%2BCVH4y8IM8PYFx2dsQOfuiILWQKLVc2JkPPH7te6HrRxh%2BzUdidwSSIXoiz%2FBZyK1Qp1Nv1yPIjCNn9ZrN0V1AKA4UlzjMY7N13IDKbHjyxXoA5291%2FtzH7I%2FApPet%2FHNawx65hli61FMXeSaTUH%2FMubtvlYU0LfcA1t5cl%2BAO%2FfxGlW%2FVQ1ipsoBCVgJLQ2XHo7385%2BwI%3D"); + UriBuilder builder = UriBuilder.fromUri("http://localhost:8180/auth/realms/demo/protocol/saml?SAMLRequest=jZJdS8MwFIbvBf9DyX2XNG62hnUwHeLAj7JNL7yRmJ65QJrUnNSPf29WHQp%2BIOQiJM%2FJed%2F3ZIyyMa2YdmFjF%2FDYAYbkpTEWRX9Rks5b4SRqFFY2gCIosZxenAs%2BYKL1LjjlDHkv%2BRuWiOCDdpYk0932xFnsGvBL8E9awfXivCSbEFpBqXFKmo3DIApeMApNa9wrACXJLGrUVm7rf6KzSMtoh3qQpkFaQ%2BPoTinduiLJqfMKes8lWUuDQJL5rCTz2d2wLmCkgKc5Z4fpMOf3qSyO8pTXxUHOjphibBRhrKId%2FQSf5YgdzC0GaUNJOMtGKTtI2eGKcxFXlg%2BK0fCWJNWHkGNta20f%2Fo7s%2Fh1CcbZaVWl1tVyR5AY89s4jQCb7e%2BOtI9G3918m999ZTL4HyIrsM%2B4x%2FfL%2Brl0rLuOT81nljFavydQY93wS4w4xj%2BA76ANuZPhdRDbI%2BhNdp%2BseFZ3FFpRea6gJ3Tai33%2Fm5A0%3D"); + builder.queryParam("RelayState", RELAY_STATE); + resp.setHeader("Location", builder.build().toString()); + return; + } + + System.out.println("Response was received"); + samlResponse = req.getParameter("SAMLResponse"); + sentRelayState = req.getParameter("RelayState"); + + PrintWriter pw = resp.getWriter(); + pw.println("Relay state: " + sentRelayState); + pw.println("SAML response: " + samlResponse); + pw.flush(); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java index 58feae3080..6e70f15851 100755 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/adapter/servlet/SendUsernameServlet.java @@ -19,6 +19,10 @@ package org.keycloak.testsuite.adapter.servlet; import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.adapters.saml.SamlAuthenticationError; +import org.keycloak.adapters.saml.SamlPrincipal; +import org.keycloak.adapters.spi.AuthenticationError; +import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -28,6 +32,9 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; /** * @author Bill Burke @@ -38,6 +45,9 @@ import java.security.Principal; public class SendUsernameServlet { private static boolean checkRoles = false; + private static SamlAuthenticationError authError; + private static Principal sentPrincipal; + private static List checkRolesList = Collections.singletonList("manager"); @Context private HttpServletRequest httpServletRequest; @@ -62,7 +72,15 @@ public class SendUsernameServlet { throw new RuntimeException("User: " + httpServletRequest.getUserPrincipal() + " do not have required role"); } - return Response.ok(getOutput(), MediaType.TEXT_PLAIN).build(); + return Response.ok(getOutput(), MediaType.TEXT_HTML_TYPE).build(); + } + + @GET + @Path("getAttributes") + public Response getSentPrincipal() throws IOException { + System.out.println("In SendUsername Servlet getSentPrincipal()"); + + return Response.ok(getAttributes(), MediaType.TEXT_HTML_TYPE).build(); } @GET @@ -79,6 +97,23 @@ public class SendUsernameServlet { return doPost(checkRolesFlag); } + @POST + @Path("error.html") + public Response errorPagePost() { + authError = (SamlAuthenticationError) httpServletRequest.getAttribute(AuthenticationError.class.getName()); + Integer statusCode = (Integer) httpServletRequest.getAttribute("javax.servlet.error.status_code"); + System.out.println("In SendUsername Servlet errorPage() status code: " + statusCode); + + return Response.ok(getErrorOutput(statusCode), MediaType.TEXT_HTML_TYPE).build(); + } + + @GET + @Path("error.html") + public Response errorPageGet() { + return errorPagePost(); + } + + @GET @Path("checkRoles") public String checkRolesEndPoint() { @@ -87,8 +122,35 @@ public class SendUsernameServlet { return "Roles will be checked"; } + @GET + @Path("uncheckRoles") + public String uncheckRolesEndPoint() { + checkRoles = false; + System.out.println("Setting checkRoles to false"); + checkRolesList = Collections.singletonList("manager"); + return "Roles will not be checked"; + } + + @GET + @Path("setCheckRoles") + public String setCheckRoles(@QueryParam("roles") String roles) { + checkRolesList = Arrays.asList(roles.split(",")); + checkRoles = true; + System.out.println("Setting checkRolesList to " + checkRolesList.toString()); + return "These roles will be checked: " + checkRolesList.toString(); + } + + private boolean checkRoles() { - return httpServletRequest.isUserInRole("manager"); + for (String role : checkRolesList) { + System.out.println("In checkRoles() checking role " + role + " for user " + httpServletRequest.getUserPrincipal().getName()); + if (!httpServletRequest.isUserInRole(role)) { + System.out.println("User is not in role " + role); + return false; + } + } + + return true; } private String getOutput() { @@ -102,6 +164,31 @@ public class SendUsernameServlet { return output + "null"; } + sentPrincipal = principal; + return output + principal.getName(); } + + private String getErrorOutput(Integer statusCode) { + String output = "Error Page

There was an error

"; + if (statusCode != null) + output += "
HTTP status code: " + statusCode; + if (authError != null) + output += "
Error info: " + authError.toString(); + return output + ""; + } + + private String getAttributes() { + SamlPrincipal principal = (SamlPrincipal) sentPrincipal; + String output = "attribute email: " + principal.getAttribute(X500SAMLProfileConstants.EMAIL.get()); + output += "
topAttribute: " + principal.getAttribute("topAttribute"); + output += "
level2Attribute: " + principal.getAttribute("level2Attribute"); + output += "
group: " + principal.getAttributes("group").toString(); + output += "
friendlyAttribute email: " + principal.getFriendlyAttribute("email"); + output += "
phone: " + principal.getAttribute("phone"); + output += "
friendlyAttribute phone: " + principal.getFriendlyAttribute("phone"); + output += "
hardcoded-attribute: " + principal.getAttribute("hardcoded-attribute"); + + return output; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java index 68d7183a06..9cd7625cc1 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java @@ -17,7 +17,6 @@ package org.keycloak.testsuite.arquillian; -import org.apache.commons.io.IOUtils; import org.apache.tools.ant.DirectoryScanner; import org.jboss.arquillian.container.test.spi.client.deployment.ApplicationArchiveProcessor; import org.jboss.arquillian.test.spi.TestClass; @@ -31,6 +30,7 @@ import org.keycloak.testsuite.arquillian.annotation.UseServletFilter; import org.keycloak.testsuite.util.IOUtil; import org.keycloak.util.JsonSerialization; import org.w3c.dom.Document; +import org.w3c.dom.Element; import javax.xml.transform.TransformerException; import java.io.File; @@ -112,6 +112,12 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { log.error("Can't transform document to String"); throw new RuntimeException(e); } + + // For running SAML tests it is necessary to have few dependencies on app-server side. + // Few of them are not in adapter zip so we need to add them manually here + log.info("Adding SAMLFilter dependencies to " + archive.getName()); + ((WebArchive) archive).addAsLibraries(KeycloakDependenciesResolver.resolveDependencies("org.keycloak:keycloak-saml-servlet-filter-adapter:" + System.getProperty("project.version"))); + } else { // OIDC adapter config try { AdapterConfig adapterConfig = loadJson(archive.get(adapterConfigPath) @@ -156,45 +162,58 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { protected void modifyWebXml(Archive archive, TestClass testClass) { try { - String webXmlContent = IOUtils.toString( + Document webXmlDoc = loadXML( archive.get(WEBXML_PATH).getAsset().openStream()); if (isTomcatAppServer(testClass.getJavaClass())) { - webXmlContent = webXmlContent.replace("KEYCLOAK", "BASIC"); + modifyDocElementValue(webXmlDoc, "auth-method", "KEYCLOAK", "BASIC"); } if (testClass.getJavaClass().isAnnotationPresent(UseServletFilter.class)) { //We need to add filter declaration to web.xml log.info("Adding filter to " + testClass.getAnnotation(UseServletFilter.class).filterClass() + " with mapping " + testClass.getAnnotation(UseServletFilter.class).filterPattern() + " for " + archive.getName()); - String filter = "\n\n" + - "" + testClass.getAnnotation(UseServletFilter.class).filterName() + "\n" + - "" + testClass.getAnnotation(UseServletFilter.class).filterClass() + "\n" + - "\n" + - "\n\n" + - "" + testClass.getAnnotation(UseServletFilter.class).filterName() + "\n" + - "" + testClass.getAnnotation(UseServletFilter.class).filterPattern() + "\n"; + + Element filter = webXmlDoc.createElement("filter"); + Element filterName = webXmlDoc.createElement("filter-name"); + Element filterClass = webXmlDoc.createElement("filter-class"); + + filterName.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterName()); + filterClass.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterClass()); + + filter.appendChild(filterName); + filter.appendChild(filterClass); + appendChildInDocument(webXmlDoc, "web-app", filter); + + Element filterMapping = webXmlDoc.createElement("filter-mapping"); + + + Element urlPattern = webXmlDoc.createElement("url-pattern"); + + filterName = webXmlDoc.createElement("filter-name"); + + filterName.setTextContent(testClass.getAnnotation(UseServletFilter.class).filterName()); + urlPattern.setTextContent(getElementTextContent(webXmlDoc, "web-app/security-constraint/web-resource-collection/url-pattern")); + + filterMapping.appendChild(filterName); + filterMapping.appendChild(urlPattern); + if (!testClass.getAnnotation(UseServletFilter.class).dispatcherType().isEmpty()) { - filter += "" + testClass.getAnnotation(UseServletFilter.class).dispatcherType() + "\n"; + Element dispatcher = webXmlDoc.createElement("dispatcher"); + dispatcher.setTextContent(testClass.getAnnotation(UseServletFilter.class).dispatcherType()); + filterMapping.appendChild(dispatcher); } - filter += "\n"; - - webXmlContent = webXmlContent.replace("", " " + filter); - - //Also we need to add all dependencies within war lib directory, because filter needs to work without installed adapter - log.info("Adding SAMLFilter dependencies to " + archive.getName()); - ((WebArchive) archive).addAsLibraries(new SAMLFilterDependency().getDependencies()); - + appendChildInDocument(webXmlDoc, "web-app", filterMapping); //finally we need to remove all keycloak related configuration from web.xml - int start = webXmlContent.indexOf(""); - int end = webXmlContent.indexOf("") + "".length(); - - - webXmlContent = webXmlContent.substring(0, start) + webXmlContent.substring(end); + removeElementFromDoc(webXmlDoc, "web-app", "security-constraint"); + removeElementFromDoc(webXmlDoc, "web-app", "login-config"); + removeElementFromDoc(webXmlDoc, "web-app", "security-role"); } - archive.add(new StringAsset((webXmlContent)), WEBXML_PATH); - } catch (IOException ex) { - throw new RuntimeException("Cannot load web.xml from archive."); + + archive.add(new StringAsset((documentToString(webXmlDoc))), WEBXML_PATH); + } catch (TransformerException e) { + log.error("Can't transform document to String"); + throw new RuntimeException(e); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakDependenciesResolver.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakDependenciesResolver.java new file mode 100644 index 0000000000..2f62c2b29e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/KeycloakDependenciesResolver.java @@ -0,0 +1,39 @@ +package org.keycloak.testsuite.arquillian; + +import org.jboss.logging.Logger; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage; +import org.jboss.shrinkwrap.resolver.api.maven.ScopeType; +import org.jboss.shrinkwrap.resolver.api.maven.coordinate.MavenDependencies; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * @author mhajas + */ +public class KeycloakDependenciesResolver { + + private static Map dependencies = new HashMap<>(); + + protected static final Logger log = org.jboss.logging.Logger.getLogger(KeycloakDependenciesResolver.class); + + public static File[] resolveDependencies(String canonicalForm) { + if (dependencies.containsKey(canonicalForm)) { + return dependencies.get(canonicalForm); + } + + log.info("Resolving " + canonicalForm + "'s dependencies"); + PomEquippedResolveStage resolver = Maven.configureResolverViaPlugin(); + + File[] files = resolver.addDependency(MavenDependencies.createDependency(canonicalForm, ScopeType.COMPILE, false)) + .resolve().withTransitivity().asFile(); + + dependencies.put(canonicalForm, files); + + log.info("Resolving dependencies is finished with " + files.length + " files"); + + return dependencies.get(canonicalForm); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SAMLFilterDependency.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SAMLFilterDependency.java deleted file mode 100644 index 808a3839da..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SAMLFilterDependency.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.keycloak.testsuite.arquillian; - -import org.jboss.logging.Logger; -import org.jboss.shrinkwrap.resolver.api.maven.Maven; -import org.jboss.shrinkwrap.resolver.api.maven.PackagingType; -import org.jboss.shrinkwrap.resolver.api.maven.ScopeType; -import org.jboss.shrinkwrap.resolver.api.maven.coordinate.MavenDependency; -import org.jboss.shrinkwrap.resolver.api.maven.coordinate.MavenDependencyExclusion; - -import java.io.File; -import java.util.Collections; -import java.util.Set; - -/** - * @author mhajas - */ -public class SAMLFilterDependency implements MavenDependency { - - private static File[] files; - - protected final Logger log = org.jboss.logging.Logger.getLogger(this.getClass()); - - @Override - public Set getExclusions() { - return Collections.EMPTY_SET; - } - - @Override - public ScopeType getScope() { - return ScopeType.COMPILE; - } - - @Override - public boolean isOptional() { - return false; - } - - @Override - public PackagingType getPackaging() { - return PackagingType.JAR; - } - - @Override - public PackagingType getType() { - return PackagingType.JAR; - } - - @Override - public String getClassifier() { - return null; - } - - @Override - public String getVersion() { - return System.getProperty("project.version"); - } - - @Override - public String getGroupId() { - return "org.keycloak"; - } - - @Override - public String getArtifactId() { - return "keycloak-saml-servlet-filter-adapter"; - } - - @Override - public String toCanonicalForm() { - return getGroupId() + ":" + getArtifactId() + ":" + getVersion(); - } - - private void resolve() { - log.info("Resolving SAMLFilter dependencies"); - files = Maven.configureResolver().addDependency(this) - .resolve().withTransitivity().asFile(); - log.info("Resolving dependencies is finished with " + files.length + " files"); - } - - public File[] getDependencies() { - if (files == null) { - resolve(); - } - - return files; - } -} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java index d8729149ac..89084d4b1c 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java @@ -16,9 +16,11 @@ */ package org.keycloak.testsuite.util; +import org.jboss.logging.Logger; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.util.JsonSerialization; import org.w3c.dom.Document; +import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -33,7 +35,6 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.*; import java.util.concurrent.TimeUnit; -import org.jboss.logging.Logger; /** * @@ -93,18 +94,99 @@ public class IOUtil { public static void modifyDocElementAttribute(Document doc, String tagName, String attributeName, String regex, String replacement) { NodeList nodes = doc.getElementsByTagName(tagName); if (nodes.getLength() != 1) { - System.out.println("Not able to find element: " + tagName); + log.warn("Not able or ambiguous to find element: " + tagName); return; } Node node = nodes.item(0).getAttributes().getNamedItem(attributeName); if (node == null) { - System.out.println("Not able to find attribute " + attributeName + " within element: " + tagName); + log.warn("Not able to find attribute " + attributeName + " within element: " + tagName); return; } node.setTextContent(node.getTextContent().replace(regex, replacement)); } + public static void modifyDocElementValue(Document doc, String tagName, String regex, String replacement) { + NodeList nodes = doc.getElementsByTagName(tagName); + if (nodes.getLength() != 1) { + log.warn("Not able or ambiguous to find element: " + tagName); + return; + } + + Node node = nodes.item(0); + if (node == null) { + log.warn("Not able to find element: " + tagName); + return; + } + + node.setTextContent(node.getTextContent().replace(regex, replacement)); + } + + public static void removeElementFromDoc(Document doc, String parentTag, String removeNode) { + NodeList nodes = doc.getElementsByTagName(parentTag); + if (nodes.getLength() != 1) { + log.warn("Not able or ambiguous to find element: " + parentTag); + return; + } + + Element parentElement = (Element) nodes.item(0); + if (parentElement == null) { + log.warn("Not able to find element: " + parentTag); + return; + } + + NodeList removeNodes = parentElement.getElementsByTagName(removeNode); + if (removeNodes.getLength() != 1) { + log.warn("Not able or ambiguous to find element: " + removeNode + " within node " + parentTag); + return; + } + + Element removeElement = (Element) removeNodes.item(0); + if (removeElement == null) { + log.warn("Not able to find element: " + removeNode + " within node " + parentTag); + return; + } + + parentElement.removeChild(removeElement); + } + + public static String getElementTextContent(Document doc, String path) { + String[] pathSegments = path.split("/"); + + Element currentElement = (Element) doc.getElementsByTagName(pathSegments[0]).item(0); + if (currentElement == null) { + log.warn("Not able to find element: " + pathSegments[0] + " in document"); + return null; + } + + for (int i = 1; i < pathSegments.length; i++) { + currentElement = (Element) currentElement.getElementsByTagName(pathSegments[i]).item(0); + + if (currentElement == null) { + log.warn("Not able to find element: " + pathSegments[i] + " in " + pathSegments[i - 1]); + return null; + } + } + + return currentElement.getTextContent(); + } + + public static void appendChildInDocument(Document doc, String parentTag, Element node) { + NodeList nodes = doc.getElementsByTagName(parentTag); + if (nodes.getLength() != 1) { + log.warn("Not able or ambiguous to find element: " + parentTag); + return; + } + + Element parentElement = (Element) nodes.item(0); + if (parentElement == null) { + log.warn("Not able to find element: " + parentTag); + return; + } + + parentElement.appendChild(node); + } + public static void execCommand(String command, File dir) throws IOException, InterruptedException { Process process = Runtime.getRuntime().exec(command, null, dir); if (process.waitFor(10, TimeUnit.SECONDS)) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterTest.java index 0ef7215866..63821b3bcc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterTest.java @@ -31,6 +31,7 @@ import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * @@ -63,10 +64,12 @@ public abstract class AbstractAdapterTest extends AbstractAuthTest { modifyClientUrls(tr, appServerContextRootPage.toString(), ""); modifyClientWebOrigins(tr, "8080", System.getProperty("auth.server.http.port", null)); modifySamlMasterURLs(tr, "/", "http://localhost:" + System.getProperty("auth.server.http.port", null) + "/"); + modifySAMLClientsAttributes(tr, "8080", System.getProperty("auth.server.http.port", "8180")); } else { modifyClientRedirectUris(tr, "^(/.*/\\*)", appServerContextRootPage.toString() + "$1"); modifyClientUrls(tr, "^(/.*)", appServerContextRootPage.toString() + "$1"); modifySamlMasterURLs(tr, "8080", System.getProperty("auth.server.http.port", null)); + modifySAMLClientsAttributes(tr, "8080", System.getProperty("app.server.http.port", "8280")); } if ("true".equals(System.getProperty("auth.server.ssl.required"))) { tr.setSslRequired("all"); @@ -125,6 +128,19 @@ public abstract class AbstractAdapterTest extends AbstractAuthTest { } } + protected void modifySAMLClientsAttributes(RealmRepresentation realm, String regex, String replacement) { + if (realm.getClients() != null) { + for (ClientRepresentation client : realm.getClients()) { + if (client.getProtocol() != null && client.getProtocol().equals("saml")) { + log.info("Modifying attributes of SAML client: " + client.getClientId()); + for (Map.Entry entry : client.getAttributes().entrySet()) { + client.getAttributes().put(entry.getKey(), entry.getValue().replaceAll(regex, replacement)); + } + } + } + } + } + protected void modifySamlMasterURLs(RealmRepresentation realm, String regex, String replacement) { if (realm.getClients() != null) { for (ClientRepresentation client : realm.getClients()) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java index badf51cd77..127c863fa8 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java @@ -25,12 +25,11 @@ import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.util.WaitUtils; import org.openqa.selenium.By; +import javax.ws.rs.core.UriBuilder; import java.io.IOException; import java.net.URL; import java.util.List; -import javax.ws.rs.core.UriBuilder; - import static org.keycloak.testsuite.auth.page.AuthRealm.DEMO; import static org.keycloak.testsuite.util.IOUtil.loadRealm; @@ -66,15 +65,15 @@ public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest { } protected static WebArchive samlServletDeployment(String name, Class... servletClasses) { - return samlServletDeployment(name, "keycloak-saml.xml", servletClasses); + return samlServletDeployment(name, "web.xml", servletClasses); } - protected static WebArchive samlServletDeployment(String name, String adapterConfig ,Class... servletClasses) { + protected static WebArchive samlServletDeployment(String name, String webXMLPath, Class... servletClasses) { String baseSAMLPath = "/adapter-test/keycloak-saml/"; String webInfPath = baseSAMLPath + name + "/WEB-INF/"; - URL keycloakSAMLConfig = AbstractServletsAdapterTest.class.getResource(webInfPath + adapterConfig); - URL webXML = AbstractServletsAdapterTest.class.getResource(baseSAMLPath + "web.xml"); + URL keycloakSAMLConfig = AbstractServletsAdapterTest.class.getResource(webInfPath + "keycloak-saml.xml"); + URL webXML = AbstractServletsAdapterTest.class.getResource(baseSAMLPath + webXMLPath); WebArchive deployment = ShrinkWrap.create(WebArchive.class, name + ".war") .addClasses(servletClasses) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java index 0df65a4f6c..268285a5b4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLFilterServletAdapterTest.java @@ -2,6 +2,8 @@ package org.keycloak.testsuite.adapter.servlet; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; import org.keycloak.testsuite.arquillian.annotation.UseServletFilter; /** @@ -25,11 +27,12 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS salesPostSigEmailServletPage.checkRoles(true); salesPostSigPersistentServletPage.checkRoles(true); salesPostSigTransientServletPage.checkRoles(true); - employee2ServletPage.navigateTo(); + salesPostAssertionAndResponseSigPage.checkRoles(true); //using endpoint instead of query param because we are not able to put query param to IDP initiated login + employee2ServletPage.navigateTo(); testRealmLoginPage.form().login(bburkeUser); - employee2ServletPage.checkRolesEndPoint(); + employee2ServletPage.checkRolesEndPoint(true); employee2ServletPage.logout(); forbiddenIfNotAuthenticated = false; @@ -51,4 +54,18 @@ public abstract class AbstractSAMLFilterServletAdapterTest extends AbstractSAMLS salesPostSigPersistentServletPage.checkRoles(false); salesPostSigTransientServletPage.checkRoles(false); } + + @Test + @Override + @Ignore + public void testSavedPostRequest() { + + } + + @Test + @Override + @Ignore + public void testErrorHandling() { + + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java index ddc23b7a1d..2973caa1dc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractSAMLServletsAdapterTest.java @@ -20,11 +20,19 @@ package org.keycloak.testsuite.adapter.servlet; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.graphene.page.Page; import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Assert; import org.junit.Test; import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ProtocolMappersResource; +import org.keycloak.protocol.saml.mappers.AttributeStatementHelper; +import org.keycloak.protocol.saml.mappers.RoleListMapper; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.saml.BaseSAML2BindingBuilder; +import org.keycloak.saml.SAML2ErrorResponseBuilder; +import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.testsuite.adapter.AbstractServletsAdapterTest; import org.keycloak.testsuite.adapter.page.*; import org.keycloak.testsuite.admin.ApiUtil; @@ -35,8 +43,15 @@ import org.keycloak.testsuite.util.IOUtil; import org.openqa.selenium.By; import org.w3c.dom.Document; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.Form; import javax.ws.rs.core.Response; +import java.net.URI; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -70,6 +85,9 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd @Page protected SalesPostServlet salesPostServletPage; + @Page + private SalesPost2Servlet salesPost2ServletPage; + @Page protected SalesPostEncServlet salesPostEncServletPage; @@ -93,6 +111,26 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd protected boolean forbiddenIfNotAuthenticated = true; + @Page + protected SalesPostAssertionAndResponseSig salesPostAssertionAndResponseSigPage; + + @Page + protected BadAssertionSalesPostSig badAssertionSalesPostSigPage; + + @Page + protected MissingAssertionSig missingAssertionSigPage; + + @Page + protected EmployeeServlet employeeServletPage; + + @Page + private InputPortal inputPortalPage; + + @Page + private SAMLIDPInitiatedLogin samlidpInitiatedLoginPage; + + public static final String FORBIDDEN_TEXT = "HTTP status code: 403"; + @Deployment(name = BadClientSalesPostSigServlet.DEPLOYMENT_NAME) protected static WebArchive badClientSalesPostSig() { return samlServletDeployment(BadClientSalesPostSigServlet.DEPLOYMENT_NAME, SendUsernameServlet.class); @@ -158,6 +196,36 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd return samlServletDeployment(SalesPostSigTransientServlet.DEPLOYMENT_NAME, SendUsernameServlet.class); } + @Deployment(name = InputPortal.DEPLOYMENT_NAME) + protected static WebArchive inputPortal() { + return samlServletDeployment(InputPortal.DEPLOYMENT_NAME, "input-portal/WEB-INF/web.xml" , InputServlet.class); + } + + @Deployment(name = SalesPost2Servlet.DEPLOYMENT_NAME) + protected static WebArchive salesPost2() { + return samlServletDeployment(SalesPost2Servlet.DEPLOYMENT_NAME, SendUsernameServlet.class); + } + + @Deployment(name = SalesPostAssertionAndResponseSig.DEPLOYMENT_NAME) + protected static WebArchive salesPostAssertionAndResponseSig() { + return samlServletDeployment(SalesPostAssertionAndResponseSig.DEPLOYMENT_NAME, SendUsernameServlet.class); + } + + @Deployment(name = BadAssertionSalesPostSig.DEPLOYMENT_NAME) + protected static WebArchive badAssertionSalesPostSig() { + return samlServletDeployment(BadAssertionSalesPostSig.DEPLOYMENT_NAME, SendUsernameServlet.class); + } + + @Deployment(name = MissingAssertionSig.DEPLOYMENT_NAME) + protected static WebArchive missingAssertionSig() { + return samlServletDeployment(MissingAssertionSig.DEPLOYMENT_NAME, SendUsernameServlet.class); + } + + @Deployment(name = EmployeeServlet.DEPLOYMENT_NAME) + protected static WebArchive employeeServlet() { + return samlServletDeployment(EmployeeServlet.DEPLOYMENT_NAME, "employee/WEB-INF/web.xml", SamlSPFacade.class); + } + @Override public void addAdapterTestRealms(List testRealms) { testRealms.add(loadRealm("/adapter-test/keycloak-saml/testsaml.json")); @@ -171,38 +239,54 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd testRealmSAMLPostLoginPage.setAuthRealm(SAMLSERVLETDEMO); } - private void assertForbidden(AbstractPage page) { + private void assertForbidden(AbstractPage page, String expectedNotContains) { page.navigateTo(); - waitUntilElement(By.xpath("//body")).text().not().contains("principal="); - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403")); + waitUntilElement(By.xpath("//body")).text().not().contains(expectedNotContains); + assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); } - private void assertSuccessfullyLoggedIn(AbstractPage page) { + private void assertSuccessfullyLoggedIn(AbstractPage page, String expectedText) { page.navigateTo(); - waitUntilElement(By.xpath("//body")).text().contains("principal=bburke"); + waitUntilElement(By.xpath("//body")).text().contains(expectedText); } - private void assertForbiddenLogin(AbstractPage page, String username, String password, Login loginPage) { + private void assertForbiddenLogin(AbstractPage page, String username, String password, Login loginPage, String expectedNotContains) { page.navigateTo(); assertCurrentUrlStartsWith(loginPage); loginPage.form().login(username, password); - waitUntilElement(By.xpath("//body")).text().not().contains("principal="); + waitUntilElement(By.xpath("//body")).text().not().contains(expectedNotContains); //Different 403 status page on EAP and Wildfly - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403")); + assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); } - private void assertSuccessfulLogin(AbstractPage page, UserRepresentation user, Login loginPage) { + private void assertSuccessfulLogin(AbstractPage page, UserRepresentation user, Login loginPage, String expectedString) { page.navigateTo(); assertCurrentUrlStartsWith(loginPage); loginPage.form().login(user); - waitUntilElement(By.xpath("//body")).text().contains("principal=bburke"); + waitUntilElement(By.xpath("//body")).text().contains(expectedString); } private void testSuccessfulAndUnauthorizedLogin(SAMLServlet page, Login loginPage) { - assertSuccessfulLogin(page, bburkeUser, loginPage); + testSuccessfulAndUnauthorizedLogin(page, loginPage, "principal=bburke"); + } + + private void testSuccessfulAndUnauthorizedLogin(SAMLServlet page, Login loginPage, String expectedText) { + testSuccessfulAndUnauthorizedLogin(page, loginPage, expectedText, "principal="); + } + + private void testSuccessfulAndUnauthorizedLogin(SAMLServlet page, Login loginPage, String expectedText, String expectedNotContains) { + assertSuccessfulLogin(page, bburkeUser, loginPage, expectedText); page.logout(); - assertForbiddenLogin(page, "unauthorized", "password", loginPage); + checkLoggedOut(page, loginPage); + assertForbiddenLogin(page, "unauthorized", "password", loginPage, expectedNotContains); page.logout(); + checkLoggedOut(page, loginPage); + } + + private void checkLoggedOut(AbstractPage page, Login loginPage) { + page.navigateTo(); + waitUntilElement(By.xpath("//body")).is().present(); + assertCurrentUrlStartsWith(loginPage); } @Test @@ -221,38 +305,35 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd @Test public void unauthorizedSSOTest() { - assertForbiddenLogin(salesPostServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage); - assertForbidden(employee2ServletPage); - assertForbidden(employeeSigFrontServletPage); - assertForbidden(salesPostSigPersistentServletPage); + assertForbiddenLogin(salesPostServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage, "principal="); + assertForbidden(employee2ServletPage, "principal="); + assertForbidden(employeeSigFrontServletPage, "principal="); + assertForbidden(salesPostSigPersistentServletPage, "principal="); salesPostServletPage.logout(); + checkLoggedOut(salesPostServletPage, testRealmSAMLPostLoginPage); } @Test public void singleLoginAndLogoutSAMLTest() { - assertSuccessfulLogin(salesPostServletPage, bburkeUser, testRealmSAMLPostLoginPage); - assertSuccessfullyLoggedIn(salesPostSigServletPage); - assertSuccessfullyLoggedIn(employee2ServletPage); - assertSuccessfullyLoggedIn(salesPostEncServletPage); + assertSuccessfulLogin(salesPostServletPage, bburkeUser, testRealmSAMLPostLoginPage, "principal=bburke"); + assertSuccessfullyLoggedIn(salesPostSigServletPage, "principal=bburke"); + assertSuccessfullyLoggedIn(employee2ServletPage, "principal=bburke"); + assertSuccessfullyLoggedIn(salesPostEncServletPage, "principal=bburke"); employeeSigFrontServletPage.logout(); - employeeSigFrontServletPage.navigateTo(); - assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage); - - employeeSigServletPage.navigateTo(); - assertCurrentUrlStartsWith(testRealmSAMLRedirectLoginPage); + checkLoggedOut(employeeSigFrontServletPage, testRealmSAMLRedirectLoginPage); + checkLoggedOut(employeeSigServletPage, testRealmSAMLRedirectLoginPage); salesPostPassiveServletPage.navigateTo(); if (forbiddenIfNotAuthenticated) { waitUntilElement(By.xpath("//body")).text().not().contains("principal="); - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("") || driver.getPageSource().equals("")); + assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); } else { waitUntilElement(By.xpath("//body")).text().contains("principal=null"); } - salesPostSigEmailServletPage.navigateTo(); - assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + checkLoggedOut(salesPostSigEmailServletPage, testRealmSAMLPostLoginPage); } @Test @@ -268,7 +349,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd waitUntilElement(By.xpath("//body")).text().not().contains("principal="); //Different 403 status page on EAP and Wildfly - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403")); + assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); } @Test @@ -328,14 +409,14 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd if (forbiddenIfNotAuthenticated) { waitUntilElement(By.xpath("//body")).text().not().contains("principal="); //Different 403 status page on EAP and Wildfly - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("") || driver.getPageSource().equals("")); + assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); } else { waitUntilElement(By.xpath("//body")).text().contains("principal=null"); } - assertSuccessfulLogin(salesPostServletPage, bburkeUser, testRealmSAMLPostLoginPage); + assertSuccessfulLogin(salesPostServletPage, bburkeUser, testRealmSAMLPostLoginPage, "principal=bburke"); - assertSuccessfullyLoggedIn(salesPostPassiveServletPage); + assertSuccessfullyLoggedIn(salesPostPassiveServletPage, "principal=bburke"); salesPostPassiveServletPage.logout(); salesPostPassiveServletPage.navigateTo(); @@ -343,12 +424,13 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd if (forbiddenIfNotAuthenticated) { waitUntilElement(By.xpath("//body")).text().not().contains("principal="); //Different 403 status page on EAP and Wildfly - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("") || driver.getPageSource().equals("")); + assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); } else { waitUntilElement(By.xpath("//body")).text().contains("principal=null"); } - assertForbiddenLogin(salesPostServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage); - assertForbidden(salesPostPassiveServletPage); + + assertForbiddenLogin(salesPostServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage, "principal="); + assertForbidden(salesPostPassiveServletPage, "principal="); salesPostPassiveServletPage.logout(); } @@ -360,7 +442,7 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd @Test public void salesPostSigEmailTest() { - testSuccessfulAndUnauthorizedLogin(salesPostSigEmailServletPage, testRealmSAMLPostLoginPage); + testSuccessfulAndUnauthorizedLogin(salesPostSigEmailServletPage, testRealmSAMLPostLoginPage, "principal=bburke@redhat.com"); } @Test @@ -371,9 +453,11 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd waitUntilElement(By.xpath("//body")).text().contains("principal=G-"); salesPostSigPersistentServletPage.logout(); + checkLoggedOut(salesPostSigPersistentServletPage, testRealmSAMLPostLoginPage); - assertForbiddenLogin(salesPostSigPersistentServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage); + assertForbiddenLogin(salesPostSigPersistentServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage, "principal="); salesPostSigPersistentServletPage.logout(); + checkLoggedOut(salesPostSigPersistentServletPage, testRealmSAMLPostLoginPage); } @Test @@ -384,36 +468,260 @@ public abstract class AbstractSAMLServletsAdapterTest extends AbstractServletsAd waitUntilElement(By.xpath("//body")).text().contains("principal=G-"); salesPostSigTransientServletPage.logout(); + checkLoggedOut(salesPostSigTransientServletPage, testRealmSAMLPostLoginPage); - assertForbiddenLogin(salesPostSigTransientServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage); + assertForbiddenLogin(salesPostSigTransientServletPage, "unauthorized", "password", testRealmSAMLPostLoginPage, "principal="); salesPostSigTransientServletPage.logout(); + checkLoggedOut(salesPostSigTransientServletPage, testRealmSAMLPostLoginPage); } @Test public void idpInitiatedLogin() { - samlidpInitiatedLogin.setAuthRealm(SAMLSERVLETDEMO); - samlidpInitiatedLogin.setUrlName("employee2"); - samlidpInitiatedLogin.navigateTo(); - samlidpInitiatedLogin.form().login(bburkeUser); + samlidpInitiatedLoginPage.setAuthRealm(SAMLSERVLETDEMO); + samlidpInitiatedLoginPage.setUrlName("employee2"); + samlidpInitiatedLoginPage.navigateTo(); + samlidpInitiatedLoginPage.form().login(bburkeUser); waitUntilElement(By.xpath("//body")).text().contains("principal=bburke"); - assertSuccessfullyLoggedIn(salesPostSigServletPage); + assertSuccessfullyLoggedIn(salesPostSigServletPage, "principal=bburke"); employee2ServletPage.logout(); + checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage); } @Test public void idpInitiatedUnauthorizedLoginTest() { - samlidpInitiatedLogin.setAuthRealm(SAMLSERVLETDEMO); - samlidpInitiatedLogin.setUrlName("employee2"); - samlidpInitiatedLogin.navigateTo(); - samlidpInitiatedLogin.form().login("unauthorized", "password"); + samlidpInitiatedLoginPage.setAuthRealm(SAMLSERVLETDEMO); + samlidpInitiatedLoginPage.setUrlName("employee2"); + samlidpInitiatedLoginPage.navigateTo(); + samlidpInitiatedLoginPage.form().login("unauthorized", "password"); waitUntilElement(By.xpath("//body")).text().not().contains("bburke"); - assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains("Status 403")); + assertTrue(driver.getPageSource().contains("Forbidden") || driver.getPageSource().contains(FORBIDDEN_TEXT)); - assertForbidden(employee2ServletPage); + assertForbidden(employee2ServletPage, "principal="); + employee2ServletPage.logout(); + checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage); + } + + @Test + public void testSavedPostRequest() { + inputPortalPage.navigateTo(); + assertCurrentUrlStartsWith(inputPortalPage); + inputPortalPage.execute("hello"); + + assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + testRealmLoginPage.form().login("bburke@redhat.com", "password"); + Assert.assertEquals(driver.getCurrentUrl(), inputPortalPage + "/secured/post"); + waitUntilElement(By.xpath("//body")).text().contains("parameter=hello"); + + // test that user principal and KeycloakSecurityContext available + driver.navigate().to(inputPortalPage + "/insecure"); + waitUntilElement(By.xpath("//body")).text().contains("Insecure Page"); + + if (System.getProperty("insecure.user.principal.unsupported") == null) waitUntilElement(By.xpath("//body")).text().contains("UserPrincipal"); + + // test logout + + inputPortalPage.logout(); + + // test unsecured POST KEYCLOAK-901 + + Client client = ClientBuilder.newClient(); + Form form = new Form(); + form.param("parameter", "hello"); + String text = client.target(inputPortalPage + "/unsecured").request().post(Entity.form(form), String.class); + Assert.assertTrue(text.contains("parameter=hello")); + client.close(); + } + + @Test + public void testPostSimpleLoginLogoutIdpInitiatedRedirectTo() { + samlidpInitiatedLoginPage.setAuthRealm(SAMLSERVLETDEMO); + samlidpInitiatedLoginPage.setUrlName("sales-post2"); + samlidpInitiatedLoginPage.navigateTo(); + + samlidpInitiatedLoginPage.form().login(bburkeUser); + assertCurrentUrlStartsWith(salesPost2ServletPage); + assertTrue(driver.getCurrentUrl().endsWith("/foo")); + waitUntilElement(By.xpath("//body")).text().contains("principal=bburke"); + salesPost2ServletPage.logout(); + checkLoggedOut(salesPost2ServletPage, testRealmSAMLPostLoginPage); + } + + @Test + public void salesPostAssertionAndResponseSigTest() { + testSuccessfulAndUnauthorizedLogin(salesPostAssertionAndResponseSigPage, testRealmSAMLPostLoginPage); + } + + @Test + public void testPostBadAssertionSignature() { + badAssertionSalesPostSigPage.navigateTo(); + assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + testRealmSAMLPostLoginPage.form().login("bburke", "password"); + + waitUntilElement(By.xpath("//body")).text().contains("Error info: SamlAuthenticationError [reason=INVALID_SIGNATURE, status=null]"); + assertEquals(driver.getCurrentUrl(), badAssertionSalesPostSigPage + "/saml"); + } + + @Test + public void testMissingAssertionSignature() { + missingAssertionSigPage.navigateTo(); + assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + testRealmSAMLPostLoginPage.form().login("bburke", "password"); + + waitUntilElement(By.xpath("//body")).text().contains("Error info: SamlAuthenticationError [reason=INVALID_SIGNATURE, status=null]"); + assertEquals(driver.getCurrentUrl(), missingAssertionSigPage + "/saml"); + } + + @Test + public void testErrorHandling() throws Exception { + Client client = ClientBuilder.newClient(); + // make sure + Response response = client.target(employeeSigServletPage.toString()).request().get(); + response.close(); + SAML2ErrorResponseBuilder builder = new SAML2ErrorResponseBuilder() + .destination(employeeSigServletPage.toString() + "/saml") + .issuer("http://localhost:" + System.getProperty("auth.server.http.port", "8180") + "/realms/demo") + .status(JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get()); + BaseSAML2BindingBuilder binding = new BaseSAML2BindingBuilder() + .relayState(null); + Document document = builder.buildDocument(); + URI uri = binding.redirectBinding(document).generateURI(employeeSigServletPage.toString() + "/saml", false); + response = client.target(uri).request().get(); + String errorPage = response.readEntity(String.class); + response.close(); + Assert.assertTrue(errorPage.contains("Error info: SamlAuthenticationError [reason=ERROR_STATUS")); + Assert.assertFalse(errorPage.contains("status=null")); + client.close(); + } + + @Test + public void testRelayStateEncoding() throws Exception { + // this test has a hardcoded SAMLRequest and we hack a SP face servlet to get the SAMLResponse so we can look + // at the relay state + employeeServletPage.navigateTo(); + assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + testRealmSAMLPostLoginPage.form().login("bburke", "password"); + assertCurrentUrlStartsWith(employeeServletPage); + waitUntilElement(By.xpath("//body")).text().contains("Relay state: " + SamlSPFacade.RELAY_STATE); + waitUntilElement(By.xpath("//body")).text().not().contains("SAML response: null"); + } + + @Test + public void testAttributes() throws Exception { + ClientResource clientResource = ApiUtil.findClientResourceByClientId(testRealmResource(), "http://localhost:8081/employee2/"); + ProtocolMappersResource protocolMappersResource = clientResource.getProtocolMappers(); + + Map config = new LinkedHashMap<>(); + config.put("attribute.nameformat", "Basic"); + config.put("user.attribute", "topAttribute"); + config.put("attribute.name", "topAttribute"); + createProtocolMapper(protocolMappersResource, "topAttribute", "saml", "saml-user-attribute-mapper", config); + + config = new LinkedHashMap<>(); + config.put("attribute.nameformat", "Basic"); + config.put("user.attribute", "level2Attribute"); + config.put("attribute.name", "level2Attribute"); + createProtocolMapper(protocolMappersResource, "level2Attribute", "saml", "saml-user-attribute-mapper", config); + + config = new LinkedHashMap<>(); + config.put("attribute.nameformat", "Basic"); + config.put("single", "true"); + config.put("attribute.name", "group"); + createProtocolMapper(protocolMappersResource, "groups", "saml", "saml-group-membership-mapper", config); + + setRolesToCheck("manager,user"); + + employee2ServletPage.navigateTo(); + assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + testRealmSAMLPostLoginPage.form().login("level2GroupUser", "password"); + + driver.navigate().to(employee2ServletPage.toString() + "/getAttributes"); + waitUntilElement(By.xpath("//body")).text().contains("topAttribute: true"); + waitUntilElement(By.xpath("//body")).text().contains("level2Attribute: true"); + waitUntilElement(By.xpath("//body")).text().contains("attribute email: level2@redhat.com"); + waitUntilElement(By.xpath("//body")).text().not().contains("group: []"); + waitUntilElement(By.xpath("//body")).text().not().contains("group: null"); + waitUntilElement(By.xpath("//body")).text().contains("group: [level2]"); + + employee2ServletPage.logout(); + checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage); + + setRolesToCheck("manager,employee,user"); + + employee2ServletPage.navigateTo(); + assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + testRealmSAMLPostLoginPage.form().login(bburkeUser); + + driver.navigate().to(employee2ServletPage.toString() + "/getAttributes"); + waitUntilElement(By.xpath("//body")).text().contains("attribute email: bburke@redhat.com"); + waitUntilElement(By.xpath("//body")).text().contains("friendlyAttribute email: bburke@redhat.com"); + waitUntilElement(By.xpath("//body")).text().contains("phone: 617"); + waitUntilElement(By.xpath("//body")).text().contains("friendlyAttribute phone: null"); + + employee2ServletPage.logout(); + checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage); + + config = new LinkedHashMap<>(); + config.put("attribute.value", "hard"); + config.put("attribute.nameformat", "Basic"); + config.put("attribute.name", "hardcoded-attribute"); + createProtocolMapper(protocolMappersResource, "hardcoded-attribute", "saml", "saml-hardcode-attribute-mapper", config); + + config = new LinkedHashMap<>(); + config.put("role", "hardcoded-role"); + createProtocolMapper(protocolMappersResource, "hardcoded-role", "saml", "saml-hardcode-role-mapper", config); + + config = new LinkedHashMap<>(); + config.put("new.role.name", "pee-on"); + config.put("role", "http://localhost:8081/employee/.employee"); + createProtocolMapper(protocolMappersResource, "renamed-employee-role", "saml", "saml-role-name-mapper", config); + + for (ProtocolMapperRepresentation mapper : clientResource.toRepresentation().getProtocolMappers()) { + if (mapper.getName().equals("role-list")) { + protocolMappersResource.delete(mapper.getId()); + + mapper.setId(null); + mapper.getConfig().put(RoleListMapper.SINGLE_ROLE_ATTRIBUTE, "true"); + mapper.getConfig().put(AttributeStatementHelper.SAML_ATTRIBUTE_NAME, "memberOf"); + protocolMappersResource.createMapper(mapper); + } + } + + setRolesToCheck("pee-on,el-jefe,manager,hardcoded-role"); + + config = new LinkedHashMap<>(); + config.put("new.role.name", "el-jefe"); + config.put("role", "user"); + createProtocolMapper(protocolMappersResource, "renamed-role", "saml", "saml-role-name-mapper", config); + + employee2ServletPage.navigateTo(); + assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + testRealmSAMLPostLoginPage.form().login(bburkeUser); + + driver.navigate().to(employee2ServletPage.toString() + "/getAttributes"); + waitUntilElement(By.xpath("//body")).text().contains("hardcoded-attribute: hard"); + employee2ServletPage.checkRolesEndPoint(false); + employee2ServletPage.logout(); + checkLoggedOut(employee2ServletPage, testRealmSAMLPostLoginPage); + } + + private void createProtocolMapper(ProtocolMappersResource resource, String name, String protocol, String protocolMapper, Map config) { + ProtocolMapperRepresentation representation = new ProtocolMapperRepresentation(); + representation.setName(name); + representation.setProtocol(protocol); + representation.setProtocolMapper(protocolMapper); + representation.setConfig(config); + resource.createMapper(representation); + } + + private void setRolesToCheck(String roles) { + employee2ServletPage.navigateTo(); + assertCurrentUrlStartsWith(testRealmSAMLPostLoginPage); + testRealmSAMLPostLoginPage.form().login(bburkeUser); + driver.navigate().to(employee2ServletPage.toString() + "/setCheckRoles?roles=" + roles); employee2ServletPage.logout(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keycloak-saml.xml new file mode 100755 index 0000000000..2ea7ff73c6 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keycloak-saml.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/bad-assertion-sales-post-sig/WEB-INF/keystore.jks new file mode 100755 index 0000000000000000000000000000000000000000..215384cdef8f651b392ec1ce469adffdb9e4a521 GIT binary patch literal 1715 zcmezO_TO6u1_mZ5W@OOMC@Cqh($~+)PfpCq$S*FjvM{hP)K5xG(Je|%%*oX)PRvOy z)-3?a=oV+D>oYJgdg`0JY6hCI)1ZlIs{tPymo^(C3!@g3AR{9y14|Rr!ngU6+@;2yEnn$CTh&C>rHT&=lxf7P=#(#EPPciy^NpNpfm&fy6 zyHq@QWgN7Quua3{_deg{5xT!T;gZa`ORyevO_>{(A?_lqLBk?tVwv zcw05^>a_ok{2o4|X}ww9d&8OTI&1rWOV}o*G;4YIABj?U{IX!F^}2T#k9r-Lx43t4 zQ}MGEKFMrHSIIbi`NFw8qTYK#;l|L+6G_!~Ze99NeR6}~B({al`YhKLe)`LEQRrFT zI??kV7ys(_Gp;rdEi+?(kYl*_{)B`XhP%wqZ|jP#(p9}*eJ-*oDJ$~t`8iuYbpKr8 z6ERK5=7)pJsncv*Ui>=Sy+-oq%Zeq3{C<3p>{zWB-*@PMv~;_~ehIyzqM#1~lg%nO zR3z>d&q_N!SG9DT%34*A@h*@U6*qn zHy^6Hte@wl)-yqsiJ6gs5!rdbKw}2Ft9#bDh1-g6C9hApR-N{BU2-$a@;~dg7G7KQ zQJ(SQaf$cVkLJ}o)C&BrQ}J_}Sd^S!pj9u%vFqgL#g4H*SOanK#kCk$R#$8fp=e2%%0nLGWF^C(9e+@>P#BW zdNL$zHF&ktAn>}*$#cAlQ?j&eY5qOdbG-`=RZ1|_=kVfH7Wht?MK{y+;BacmesiaSi9C`rL(tv zt7e>A!hGn3)+w + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/web.xml new file mode 100644 index 0000000000..019958832b --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/employee/WEB-INF/web.xml @@ -0,0 +1,54 @@ + + + + + + %CONTEXT_PATH% + + + Servlet + org.keycloak.testsuite.adapter.servlet.SamlSPFacade + + + + Servlet + /* + + + + + Users + /* + + + manager + + + + + KEYCLOAK-SAML + demo + + + + manager + + diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/keycloak-saml.xml new file mode 100755 index 0000000000..e1cd76a88e --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/keycloak-saml.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/web.xml new file mode 100644 index 0000000000..865dc8a170 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/input-portal/WEB-INF/web.xml @@ -0,0 +1,54 @@ + + + + + + %CONTEXT_PATH% + + + Servlet + org.keycloak.testsuite.adapter.servlet.InputServlet + + + + Servlet + /* + + + + + Users + /secured/* + + + manager + + + + + KEYCLOAK-SAML + demo + + + + manager + + diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml new file mode 100755 index 0000000000..936fb21711 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keycloak-saml.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/missing-assertion-sig/WEB-INF/keystore.jks new file mode 100755 index 0000000000000000000000000000000000000000..144830bc77683d1d0a3d29f8793471d4f395bdb0 GIT binary patch literal 1705 zcmezO_TO6u1_mZ5W@J#!C@Cqh($~+)PfpCq$S*FjvM{hP&@WERNiEhb0P=N1M z7#KYz`B=UJb?!81V%loJ$Ht}2#>m2`#U#kc$jZRd#I(>@`_HHQr3*BE{x`hDyEHld z+K~ja7y5T|r=%}Fu;`1zi=v(re>&z=o_dkol)j+zYO>t=Qy1S}v3en%vP3563G;>m zf%$6H#y=LWo_E+v&g1u@xzTso16CaPbkO_D8o@;(`MobV^Igq3BNxZ5w|jA2W>>?O zsK)o5D^C2naN_4wlZOiFZx$ZYW2t4}JhWx=_GhgLGruICtx3+%d3V!9>Dt7nXAhlc z$;{X;mJr8NzU5%3ud}q*;Tc<&7_HRg&F#_Q?wWFKHOG|)@xdBe`K>+ek+V}K-){E) zl)cDAKzFrg_KD*rPhJ#0xY%188oeS%DBgct!6JRDwVn#urq)k%GX7O$-%BXGHp?M$ zPK(%~JsS=itGtnGNeXRIJS=enoqTH}>U?=&w0Rv-!V zt0IaI7=L{C`R*=P35_m^|6)DbnI1Rom?{^itz&v>PMs${YwWF4+mV{{ zB3p#_4CDJAHQ!e4#HZJ%dA%($R{WrMXd~~o9sJ4l4<@p_UZ&<;=%+j9w)>u?FXr2x z;Qi$;R(a*~n?JwFjfqj;bam5w#iu)2cStlBuRNH-DYCa*SFTj`Ruc2O%^{1v zS^p3Un$kT>@>nGEyG?KA#5`*A-Q)TDd5mCQN4nqL|I4L=R^IMCEM*}Xua~%r;rLwD z(rqehS=sAVMB2P;SIyh9w+fi%85vk3^h^ybfvg(_O^jE7*p`Wn(Np4IY>ELd8>d#A zN85K^Mn-N{1_J{_Jp&y!=1>-9Vd650ft)z6p^2e^p_!qPiHVVMlsK;ujB93K97TqS zjq{OR1z|$TGQU@sN$9wB)LN$MwU<2= zd)SlRE7$3UyG=O%*+B2uv-3&H4597J6WaBsZpb_&f7j)l$IXYTF6-xcsr5`yWnyMz zU_^EvFwmHR?&_X(ZsE4#TgmHFu2rXfU6s0)lCKe^<7iiUsaqK$zd9h>c57vO3iO-78%EoLL<8ZmZxNJeq&U-KBw5j(T{eI-t zj}(o0{2w&pRSxr|MK;+T;phADf{DX@d*|wohzX72{tZ3*&sbbC2N!KDDXF>n@KOsN z=~ICbKGC4@Hz>{}*<-lToNvHy$ZNn2iF2?C$Wdx)Vu&22rlzLAD8`N=&Wn#AY>fF{RdOu_iDQssmI(9hPO(2Uiv*4ZQlGJH!&z=`K zw%L^5bkqb1@@gO=+rT(BxQD(6^h&8PDm@hVJA%Qxx$H92KBf6;Q! zeu+xYdBwlV_8D(8vle0u7ZYR_ZJGO3A6Ja^r!9<;Jf^6B_V*`!pI4_%{2#46Qrcoa z{d4O3!wr>pceY+y88$ipliuM2X9G4KozS*BUZS)4a=%Gc^OV}R*}*rh8m{{;m0Oa) z^Ysn?xlP?KeF94gT3Fr`zuJ}MUVH6cw8xR2&A~UG*V~pyDQR4_ymoT_iPrx=pDW$^ gV7obl)$b?cUQd>i8HM*{tk@@=IXfvlri|w}0JG8182|tP literal 0 HcmV?d00001 diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml new file mode 100755 index 0000000000..94c3f4da01 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keycloak-saml.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post-assertion-and-response-sig/WEB-INF/keystore.jks new file mode 100755 index 0000000000000000000000000000000000000000..144830bc77683d1d0a3d29f8793471d4f395bdb0 GIT binary patch literal 1705 zcmezO_TO6u1_mZ5W@J#!C@Cqh($~+)PfpCq$S*FjvM{hP&@WERNiEhb0P=N1M z7#KYz`B=UJb?!81V%loJ$Ht}2#>m2`#U#kc$jZRd#I(>@`_HHQr3*BE{x`hDyEHld z+K~ja7y5T|r=%}Fu;`1zi=v(re>&z=o_dkol)j+zYO>t=Qy1S}v3en%vP3563G;>m zf%$6H#y=LWo_E+v&g1u@xzTso16CaPbkO_D8o@;(`MobV^Igq3BNxZ5w|jA2W>>?O zsK)o5D^C2naN_4wlZOiFZx$ZYW2t4}JhWx=_GhgLGruICtx3+%d3V!9>Dt7nXAhlc z$;{X;mJr8NzU5%3ud}q*;Tc<&7_HRg&F#_Q?wWFKHOG|)@xdBe`K>+ek+V}K-){E) zl)cDAKzFrg_KD*rPhJ#0xY%188oeS%DBgct!6JRDwVn#urq)k%GX7O$-%BXGHp?M$ zPK(%~JsS=itGtnGNeXRIJS=enoqTH}>U?=&w0Rv-!V zt0IaI7=L{C`R*=P35_m^|6)DbnI1Rom?{^itz&v>PMs${YwWF4+mV{{ zB3p#_4CDJAHQ!e4#HZJ%dA%($R{WrMXd~~o9sJ4l4<@p_UZ&<;=%+j9w)>u?FXr2x z;Qi$;R(a*~n?JwFjfqj;bam5w#iu)2cStlBuRNH-DYCa*SFTj`Ruc2O%^{1v zS^p3Un$kT>@>nGEyG?KA#5`*A-Q)TDd5mCQN4nqL|I4L=R^IMCEM*}Xua~%r;rLwD z(rqehS=sAVMB2P;SIyh9w+fi%85vk3^h^ybfvg(_O^jE7*p`Wn(Np4IY>ELd8>d#A zN85K^Mn-N{1_J{_Jp&y!=1>-9Vd650ft)z6p^2e^p_!qPiHVVMlsK;ujB93K97TqS zjq{OR1z|$TGQU@sN$9wB)LN$MwU<2= zd)SlRE7$3UyG=O%*+B2uv-3&H4597J6WaBsZpb_&f7j)l$IXYTF6-xcsr5`yWnyMz zU_^EvFwmHR?&_X(ZsE4#TgmHFu2rXfU6s0)lCKe^<7iiUsaqK$zd9h>c57vO3iO-78%EoLL<8ZmZxNJeq&U-KBw5j(T{eI-t zj}(o0{2w&pRSxr|MK;+T;phADf{DX@d*|wohzX72{tZ3*&sbbC2N!KDDXF>n@KOsN z=~ICbKGC4@Hz>{}*<-lToNvHy$ZNn2iF2?C$Wdx)Vu&22rlzLAD8`N=&Wn#AY>fF{RdOu_iDQssmI(9hPO(2Uiv*4ZQlGJH!&z=`K zw%L^5bkqb1@@gO=+rT(BxQD(6^h&8PDm@hVJA%Qxx$H92KBf6;Q! zeu+xYdBwlV_8D(8vle0u7ZYR_ZJGO3A6Ja^r!9<;Jf^6B_V*`!pI4_%{2#46Qrcoa z{d4O3!wr>pceY+y88$ipliuM2X9G4KozS*BUZS)4a=%Gc^OV}R*}*rh8m{{;m0Oa) z^Ysn?xlP?KeF94gT3Fr`zuJ}MUVH6cw8xR2&A~UG*V~pyDQR4_ymoT_iPrx=pDW$^ gV7obl)$b?cUQd>i8HM*{tk@@=IXfvlri|w}0JG8182|tP literal 0 HcmV?d00001 diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post2/WEB-INF/keycloak-saml.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post2/WEB-INF/keycloak-saml.xml new file mode 100755 index 0000000000..eae5b14985 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/sales-post2/WEB-INF/keycloak-saml.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json index 5a1d2c6c20..0e25d89f60 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/testsaml.json @@ -67,6 +67,101 @@ } ], "clients": [ + { + "clientId": "http://localhost:8081/missing-assertion-sig/", + "enabled": true, + "protocol": "saml", + "fullScopeAllowed": true, + "baseUrl": "http://localhost:8080/missing-assertion-sig", + "redirectUris": [ + "http://localhost:8080/missing-assertion-sig/*" + ], + "attributes": { + "saml_assertion_consumer_url_post": "http://localhost:8080/missing-assertion-sig/saml", + "saml_assertion_consumer_url_redirect": "http://localhost:8080/missing-assertion-sig/saml", + "saml_single_logout_service_url_post": "http://localhost:8080/missing-assertion-sig/saml", + "saml_single_logout_service_url_redirect": "http://localhost:8080/missing-assertion-sig/saml", + "saml.server.signature": "true", + "saml.assertion.signature": "false", + "saml.signature.algorithm": "RSA_SHA256", + "saml.client.signature": "true", + "saml.authnstatement": "true", + "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw==" + } + }, + { + "clientId": "http://localhost:8081/bad-assertion-sales-post-sig/", + "enabled": true, + "protocol": "saml", + "fullScopeAllowed": true, + "baseUrl": "http://localhost:8080/bad-assertion-sales-post-sig/", + "adminUrl": "http://localhost:8080/bad-assertion-sales-post-sig/saml", + "redirectUris": [ + "http://localhost:8080/bad-assertion-sales-post-sig/*" + ], + "attributes": { + "saml.assertion.signature": "true", + "saml.client.signature": "true", + "saml.authnstatement": "true", + "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw==" + } + }, + { + "clientId": "http://localhost:8081/input-portal/", + "enabled": true, + "fullScopeAllowed": true, + "protocol": "saml", + "baseUrl": "http://localhost:8080/input-portal", + "redirectUris": [ + "http://localhost:8080/input-portal/*" + ], + "attributes": { + "saml.authnstatement": "true", + "saml_assertion_consumer_url_post": "http://localhost:8080/input-portal/saml", + "saml_assertion_consumer_url_redirect": "http://localhost:8080/input-portal/saml", + "saml_single_logout_service_url_post": "http://localhost:8080/input-portal/saml", + "saml_single_logout_service_url_redirect": "http://localhost:8080/input-portal/saml" + } + }, + { + "clientId": "http://localhost:8081/sales-post-assertion-and-response-sig/", + "enabled": true, + "protocol": "saml", + "fullScopeAllowed": true, + "baseUrl": "http://localhost:8080/sales-post-assertion-and-response-sig", + "redirectUris": [ + "http://localhost:8080/sales-post-assertion-and-response-sig/*" + ], + "attributes": { + "saml_assertion_consumer_url_post": "http://localhost:8080/sales-post-assertion-and-response-sig/saml", + "saml_assertion_consumer_url_redirect": "http://localhost:8080/sales-post-assertion-and-response-sig/saml", + "saml_single_logout_service_url_post": "http://localhost:8080/sales-post-assertion-and-response-sig/saml", + "saml_single_logout_service_url_redirect": "http://localhost:8080/sales-post-assertion-and-response-sig/saml", + "saml.server.signature": "true", + "saml.assertion.signature": "true", + "saml.signature.algorithm": "RSA_SHA256", + "saml.client.signature": "true", + "saml.authnstatement": "true", + "saml.signing.certificate": "MIIB1DCCAT0CBgFJGP5dZDANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVodHRwOi8vbG9jYWxob3N0OjgwODAvc2FsZXMtcG9zdC1zaWcvMB4XDTE0MTAxNjEyNDQyM1oXDTI0MTAxNjEyNDYwM1owMDEuMCwGA1UEAxMlaHR0cDovL2xvY2FsaG9zdDo4MDgwL3NhbGVzLXBvc3Qtc2lnLzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1RvGu8RjemSJA23nnMksoHA37MqY1DDTxOECY4rPAd9egr7GUNIXE0y1MokaR5R2crNpN8RIRwR8phQtQDjXL82c6W+NLQISxztarQJ7rdNJIYwHY0d5ri1XRpDP8zAuxubPYiMAVYcDkIcvlbBpwh/dRM5I2eElRK+eSiaMkCUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCLms6htnPaY69k1ntm9a5jgwSn/K61cdai8R8B0ccY7zvinn9AfRD7fiROQpFyY29wKn8WCLrJ86NBXfgFUGyR5nLNHVy3FghE36N2oHy53uichieMxffE6vhkKJ4P8ChfJMMOZlmCPsQPDvjoAghHt4mriFiQgRdPgIy/zDjSNw==" + } + }, + { + "clientId": "http://localhost:8081/sales-post2/", + "enabled": true, + "fullScopeAllowed": true, + "protocol": "saml", + "baseUrl": "http://localhost:8080/sales-post2", + "redirectUris": [ + "http://localhost:8080/sales-post2/*" + ], + "attributes": { + "saml.authnstatement": "true", + "saml_assertion_consumer_url_post": "http://localhost:8080/sales-post2/saml", + "saml_single_logout_service_url_post": "http://localhost:8080/sales-post2/saml", + "saml_idp_initiated_sso_url_name": "sales-post2", + "saml_idp_initiated_sso_relay_state": "redirectTo=/foo" + } + }, { "clientId": "http://localhost:8081/sales-post/", "enabled": true, diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml index 4207f91795..aa2c4b74ab 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/keycloak-saml/web.xml @@ -28,6 +28,10 @@ /* + + /error.html + + Application diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6SAMLFilterAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6SAMLFilterAdapterTest.java index 627e5f3d8e..1dcf846eb3 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6SAMLFilterAdapterTest.java +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/eap6/src/test/java/org/keycloak/testsuite/adapter/EAP6SAMLFilterAdapterTest.java @@ -8,5 +8,5 @@ import org.keycloak.testsuite.arquillian.annotation.UseServletFilter; * @author mhajas */ @AppServerContainer("app-server-eap6") -public class EAPSAMLFilterAdapterTest extends AbstractSAMLFilterServletAdapterTest { +public class EAP6SAMLFilterAdapterTest extends AbstractSAMLFilterServletAdapterTest { } diff --git a/testsuite/integration-arquillian/tests/other/adapters/pom.xml b/testsuite/integration-arquillian/tests/other/adapters/pom.xml index 9c49390b93..15fc1c4836 100644 --- a/testsuite/integration-arquillian/tests/other/adapters/pom.xml +++ b/testsuite/integration-arquillian/tests/other/adapters/pom.xml @@ -172,6 +172,18 @@ + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-maven-plugin + ${version.shrinkwrap.resolvers} + + + + propagate-execution-context + + + + diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 6c250598a4..de3d65f4ce 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -780,6 +780,10 @@ org.keycloak keycloak-adapter-spi + + org.keycloak + keycloak-saml-adapter-api-public + From 85ac784fd09dbd3ac743d217efbe8c115a189224 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Tue, 16 Aug 2016 20:34:48 +0900 Subject: [PATCH 06/49] KEYCLOAK-3435 Fix some minor issues in the Admin Console UI --- .../messages/admin-messages_ca.properties | 4 +-- .../messages/admin-messages_en.properties | 9 +++--- .../messages/admin-messages_es.properties | 4 +-- .../messages/admin-messages_ru.properties | 4 +-- .../resources/partials/federated-ldap.html | 2 +- .../partials/group-role-mappings.html | 3 +- .../partials/realm-events-admin.html | 2 +- .../partials/realm-events-config.html | 30 +++++++------------ .../realm-identity-provider-oidc.html | 14 ++++----- .../realm-identity-provider-saml.html | 14 ++++----- .../resources/partials/role-mappings.html | 3 +- 11 files changed, 42 insertions(+), 47 deletions(-) diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties index 02a307a04c..0025c882a2 100755 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ca.properties @@ -218,8 +218,8 @@ assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client''s assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL -logout-service-binding-post-url=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 -logout-service-binding-post-url.tooltip=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent. +logout-service-post-binding-url=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 +logout-service-post-binding-url.tooltip=URL d''enlla\u00E7 SAML POST per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent. logout-service-redir-binding-url=URL d''enlla\u00E7 SAML de redirecci\u00F3 per a la desconnexi\u00F3 logout-service-redir-binding-url.tooltip=URL d''enlla\u00E7 SAML de redirecci\u00F3 per a la desconnexi\u00F3 \u00FAnica del client. Pots deixar-ho en blanc si est\u00E0s fent servir un enlla\u00E7 diferent. diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 02c7a7039f..8a9b34809b 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -242,8 +242,8 @@ assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL assertion-consumer-redirect-binding-url.tooltip=SAML Redirect Binding URL for the client's assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. -logout-service-binding-post-url=Logout Service POST Binding URL -logout-service-binding-post-url.tooltip=SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding +logout-service-post-binding-url=Logout Service POST Binding URL +logout-service-post-binding-url.tooltip=SAML POST Binding URL for the client's single logout service. You can leave this blank if you are using a different binding logout-service-redir-binding-url=Logout Service Redirect Binding URL logout-service-redir-binding-url.tooltip=SAML Redirect Binding URL for the client's single logout service. You can leave this blank if you are using a different binding. @@ -312,7 +312,7 @@ available-roles=Available Roles add-selected=Add selected associated-roles=Associated Roles composite.associated-realm-roles.tooltip=Realm level roles associated with this composite role. -composite.available-realm-roles.tooltip=Realm level roles associated with this composite role. +composite.available-realm-roles.tooltip=Realm level roles that you can associate to this composite role. remove-selected=Remove selected client-roles=Client Roles select-client-to-view-roles=Select client to view roles for client @@ -560,7 +560,7 @@ realm-default-roles.tooltip=Realm level roles assigned to new users. default.available-roles-client.tooltip=Roles from this client that are assignable as a default. client-default-roles=Client Default Roles client-default-roles.tooltip=Roles from this client assigned as a default role. -composite.available-roles.tooltip=Realm level roles associated with this composite role. +composite.available-roles.tooltip=Realm level roles that you can associate to this composite role. composite.associated-roles.tooltip=Realm level roles associated with this composite role. composite.available-roles-client.tooltip=Roles from this client that you can associate to this composite role. composite.associated-roles-client.tooltip=Client roles associated with this composite role. @@ -802,6 +802,7 @@ top-level-flow-type.tooltip=What kind of top level flow is it? Type 'client' is create-execution-flow=Create Execution Flow flow-type=Flow Type flow.form.type=form +flow.generic.type=generic flow-type.tooltip=What kind of form is it form-provider=Form Provider default-groups.tooltip=Newly created or registered users will automatically be added to these groups diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties index 2a1807cc9a..fb17ff6a24 100755 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_es.properties @@ -218,8 +218,8 @@ assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL assertion-consumer-post-binding-url.tooltip=SAML POST Binding URL for the client''s assertion consumer service (login responses). You can leave this blank if you do not have a URL for this binding. assertion-consumer-redirect-binding-url=Assertion Consumer Service Redirect Binding URL assertion-consumer-redirect-binding-url.tooltip=Assertion Consumer Service Redirect Binding URL -logout-service-binding-post-url=URL de enlace SAML POST para la desconexi\u00F3n -logout-service-binding-post-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto. +logout-service-post-binding-url=URL de enlace SAML POST para la desconexi\u00F3n +logout-service-post-binding-url.tooltip=URL de enlace SAML POST para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto. logout-service-redir-binding-url=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n logout-service-redir-binding-url.tooltip=URL de enlace SAML de redirecci\u00F3n para la desconexi\u00F3n \u00FAnica del cliente. Puedes dejar esto en blanco si est\u00E1s usando un enlace distinto. diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties index c28da5677f..6a59cc9f36 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ru.properties @@ -233,8 +233,8 @@ assertion-consumer-post-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u assertion-consumer-post-binding-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u0438\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u0432 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F (\u0437\u0430\u043F\u0440\u043E\u0441\u044B \u0432\u0445\u043E\u0434\u0430). \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u043D\u0435 \u0438\u043C\u0435\u0435\u0442\u0435 URL \u0434\u043B\u044F \u043E\u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043B\u0435\u043D\u0438\u044F \u0442\u0430\u043A\u043E\u0439 \u0441\u0432\u044F\u0437\u043A\u0438. assertion-consumer-redirect-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438 \u0441 \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u043C \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F assertion-consumer-redirect-binding-url.tooltip=SAML \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u044F \u043D\u0430 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043D\u043E\u0433\u043E \u043F\u043E\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043B\u044F (\u0437\u0430\u043F\u0440\u043E\u0441\u044B \u0432\u0445\u043E\u0434\u0430). \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u043D\u0435 \u0438\u043C\u0435\u0435\u0442\u0435 URL \u0434\u043B\u044F \u0442\u0430\u043A\u043E\u0433\u043E \u0441\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u044F. -logout-service-binding-post-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F POST-\u043C\u0435\u0442\u043E\u0434\u0430 -logout-service-binding-post-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u0432\u044B\u0445\u043E\u0434\u0430. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438 +logout-service-post-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F POST-\u043C\u0435\u0442\u043E\u0434\u0430 +logout-service-post-binding-url.tooltip=SAML POST \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u043E\u0433\u043E \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0435\u0434\u0438\u043D\u043E\u0433\u043E \u0432\u044B\u0445\u043E\u0434\u0430. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0432\u044B \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438 logout-service-redir-binding-url=\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u043D\u0438\u0435 URL \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438 \u0434\u043B\u044F \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 logout-service-redir-binding-url.tooltip=SAML \u043F\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0443\u0435\u0442 \u043D\u0430 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0439 URL \u0434\u043B\u044F \u0435\u0434\u0438\u043D\u043E\u0439 \u0442\u043E\u0447\u043A\u0438 \u0432\u044B\u0445\u043E\u0434\u0430 \u0438\u0437 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043B\u044F \u043A\u043B\u0438\u0435\u043D\u0442\u043E\u0432. \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u044D\u0442\u043E \u043F\u043E\u043B\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0435\u0441\u043B\u0438 \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u0442\u0435 \u0440\u0430\u0437\u043B\u0438\u0447\u043D\u044B\u0435 \u0441\u0432\u044F\u0437\u0438. diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html b/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html index 49d1fae6fe..d2a75c22b6 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/federated-ldap.html @@ -232,7 +232,7 @@ {{:: 'debug.tooltip' | translate}}
- +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html index d49561e58d..bcb3150df3 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/group-role-mappings.html @@ -84,7 +84,8 @@
- + + {{:: 'group.effective-roles-client.tooltip' | translate}}
-
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html index 8f36f6a621..a1c5a8077b 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html @@ -17,14 +17,12 @@
- + {{:: 'event-listeners.tooltip' | translate}}
- -
@@ -33,41 +31,37 @@
+ {{:: 'login.save-events.tooltip' | translate}}
-
- + {{:: 'saved-types.tooltip' | translate}}
- -
+ {{:: 'clear-events.tooltip' | translate}}
-
-
+
-
+ {{:: 'events.expiration.tooltip' | translate}} +
-
-
- -
@@ -78,28 +72,26 @@
+ {{:: 'admin.save-events.tooltip' | translate}}
- -
+ {{:: 'include-representation.tooltip' | translate}}
- -
+ {{:: 'clear-admin-events.tooltip' | translate}}
-
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index 1db4c6af62..93671ef0a7 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -118,13 +118,13 @@
{{:: 'identity-provider.logout-url.tooltip' | translate}}
-
- -
- -
- -
+
+ +
+ +
+ {{:: 'backchannel-logout.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index aa94559e03..f9b8c740e9 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -109,13 +109,13 @@
{{:: 'saml.single-logout-service-url.tooltip' | translate}}
-
- -
- -
- -
+
+ +
+ +
+ {{:: 'backchannel-logout.tooltip' | translate}} +
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html index fd1a0fadca..9970be4801 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html @@ -84,7 +84,8 @@
- + + {{:: 'user.effective-roles-client.tooltip' | translate}} @@ -30,4 +30,4 @@ <#elseif section = "info" > ${msg("emailInstruction")} - \ No newline at end of file + From 4cd0a8e894896c0099157833dda49e512760a4fe Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Thu, 18 Aug 2016 13:29:54 -0300 Subject: [PATCH 10/49] [KEYCLOAK-3377] - Add pagination to authorization UI --- .../infinispan/CachedPolicyStore.java | 5 + .../infinispan/CachedResourceStore.java | 6 + .../infinispan/CachedScopeStore.java | 6 + .../jpa/store/JPAPolicyStore.java | 43 ++++- .../jpa/store/JPAResourceStore.java | 37 ++++ .../jpa/store/JPAScopeStore.java | 33 ++++ .../mongo/store/MongoPolicyStore.java | 27 +++ .../mongo/store/MongoResourceStore.java | 25 ++- .../mongo/store/MongoScopeStore.java | 18 ++ .../authorization/store/PolicyStore.java | 12 +- .../authorization/store/ResourceStore.java | 12 +- .../authorization/store/ScopeStore.java | 13 +- .../authorization/admin/PolicyService.java | 62 ++++++- .../admin/ResourceSetService.java | 62 ++++++- .../authorization/admin/ScopeService.java | 15 +- .../protection/resource/ResourceService.java | 2 +- .../resources/js/authz/authz-controller.js | 170 ++++++++++++++++-- .../resource-server-permission-list.html | 28 ++- .../policy/resource-server-policy-list.html | 28 ++- .../authz/resource-server-resource-list.html | 39 +++- .../authz/resource-server-scope-list.html | 16 +- 21 files changed, 606 insertions(+), 53 deletions(-) diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java index 4657633d67..b503bcef77 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java @@ -103,6 +103,11 @@ public class CachedPolicyStore implements PolicyStore { return getDelegate().findByResourceServer(resourceServerId).stream().map(policy -> findById(policy.getId())).collect(Collectors.toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult); + } + @Override public List findByResource(String resourceId) { List cache = new ArrayList<>(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java index 4d9a946983..f86de2f067 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java @@ -32,6 +32,7 @@ import org.keycloak.models.authorization.infinispan.entities.CachedResource; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.stream.Collectors; @@ -126,6 +127,11 @@ public class CachedResourceStore implements ResourceStore { return getDelegate().findByResourceServer(resourceServerId).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult); + } + @Override public List findByScope(String... id) { return getDelegate().findByScope(id).stream().map(resource -> findById(resource.getId())).collect(Collectors.toList()); diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java index 72f3f25ec1..f86a7d1bb8 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java @@ -30,6 +30,7 @@ import org.keycloak.models.authorization.infinispan.entities.CachedScope; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Map.Entry; /** @@ -114,6 +115,11 @@ public class CachedScopeStore implements ScopeStore { return getDelegate().findByResourceServer(id); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + return getDelegate().findByResourceServer(attributes, resourceServerId, firstResult, maxResult); + } + private String getCacheKeyForScope(String id) { return SCOPE_ID_CACHE_PREFIX + id; } diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java index 8b88ad16b7..b57cd1efd5 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAPolicyStore.java @@ -27,11 +27,15 @@ import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * @author Pedro Igor @@ -100,6 +104,43 @@ public class JPAPolicyStore implements PolicyStore { return query.getResultList(); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery querybuilder = builder.createQuery(PolicyEntity.class); + Root root = querybuilder.from(PolicyEntity.class); + List predicates = new ArrayList(); + + predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId)); + + attributes.forEach((name, value) -> { + if ("permission".equals(name)) { + if (Boolean.valueOf(value[0])) { + predicates.add(root.get("type").in("resource", "scope")); + } else { + predicates.add(builder.not(root.get("type").in("resource", "scope"))); + } + } else if ("id".equals(name)) { + predicates.add(root.get(name).in(value)); + } else { + predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); + } + }); + + querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name"))); + + Query query = entityManager.createQuery(querybuilder); + + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResult != -1) { + query.setMaxResults(maxResult); + } + + return query.getResultList(); + } + @Override public List findByResource(final String resourceId) { Query query = getEntityManager().createQuery("select p from PolicyEntity p inner join p.resources r where r.id = :resourceId"); diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java index 986d007808..6d00bb66d4 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAResourceStore.java @@ -26,8 +26,14 @@ import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; /** * @author Pedro Igor @@ -96,6 +102,37 @@ public class JPAResourceStore implements ResourceStore { return query.getResultList(); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery querybuilder = builder.createQuery(ResourceEntity.class); + Root root = querybuilder.from(ResourceEntity.class); + List predicates = new ArrayList(); + + predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId)); + + attributes.forEach((name, value) -> { + if ("scope".equals(name)) { + predicates.add(root.join("scopes").get("id").in(value)); + } else { + predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); + } + }); + + querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name"))); + + Query query = entityManager.createQuery(querybuilder); + + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResult != -1) { + query.setMaxResults(maxResult); + } + + return query.getResultList(); + } + @Override public List findByScope(String... id) { Query query = entityManager.createQuery("select r from ResourceEntity r inner join r.scopes s where s.id in (:scopeIds)"); diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java index cc9a956498..d468314374 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/store/JPAScopeStore.java @@ -27,7 +27,13 @@ import org.keycloak.models.utils.KeycloakModelUtils; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.Query; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * @author Pedro Igor @@ -85,4 +91,31 @@ public class JPAScopeStore implements ScopeStore { return query.getResultList(); } + + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + CriteriaQuery querybuilder = builder.createQuery(ScopeEntity.class); + Root root = querybuilder.from(ScopeEntity.class); + List predicates = new ArrayList(); + + predicates.add(builder.equal(root.get("resourceServer").get("id"), resourceServerId)); + + attributes.forEach((name, value) -> { + predicates.add(builder.like(builder.lower(root.get(name)), "%" + value[0].toLowerCase() + "%")); + }); + + querybuilder.where(predicates.toArray(new Predicate[predicates.size()])).orderBy(builder.asc(root.get("name"))); + + Query query = entityManager.createQuery(querybuilder); + + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResult != -1) { + query.setMaxResults(maxResult); + } + + return query.getResultList(); + } } diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java index 6f0ba5dcd7..04a3d9ac4b 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoPolicyStore.java @@ -32,6 +32,8 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import static java.util.stream.Collectors.toList; @@ -101,6 +103,31 @@ public class MongoPolicyStore implements PolicyStore { .collect(toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + QueryBuilder queryBuilder = new QueryBuilder() + .and("resourceServerId").is(resourceServerId); + + attributes.forEach((name, value) -> { + if ("permission".equals(name)) { + if (Boolean.valueOf(value[0])) { + queryBuilder.and("type").in(new String[] {"resource", "scope"}); + } else { + queryBuilder.and("type").notIn(new String[] {"resource", "scope"}); + } + } else if ("id".equals(name)) { + queryBuilder.and("_id").in(value); + } else { + queryBuilder.and(name).regex(Pattern.compile(".*" + value[0] + ".*", Pattern.CASE_INSENSITIVE)); + } + }); + + DBObject sort = new BasicDBObject("name", 1); + + return getMongoStore().loadEntities(PolicyEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() + .map(policy -> findById(policy.getId())).collect(toList()); + } + @Override public List findByResource(String resourceId) { DBObject query = new QueryBuilder() diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java index 11b735ba6c..a85de72970 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoResourceStore.java @@ -18,6 +18,7 @@ package org.keycloak.authorization.mongo.store; +import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.keycloak.authorization.AuthorizationProvider; @@ -26,12 +27,13 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.mongo.adapter.ResourceAdapter; import org.keycloak.authorization.mongo.entities.ResourceEntity; import org.keycloak.authorization.store.ResourceStore; -import org.keycloak.connections.mongo.api.MongoIdentifiableEntity; import org.keycloak.connections.mongo.api.MongoStore; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import static java.util.stream.Collectors.toList; @@ -98,10 +100,29 @@ public class MongoResourceStore implements ResourceStore { .map(scope -> findById(scope.getId())).collect(toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + QueryBuilder queryBuilder = new QueryBuilder() + .and("resourceServerId").is(resourceServerId); + + attributes.forEach((name, value) -> { + if ("scope".equals(name)) { + queryBuilder.and("scopes").in(value); + } else { + queryBuilder.and(name).regex(Pattern.compile(".*" + value[0] + ".*", Pattern.CASE_INSENSITIVE)); + } + }); + + DBObject sort = new BasicDBObject("name", 1); + + return getMongoStore().loadEntities(ResourceEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() + .map(scope -> findById(scope.getId())).collect(toList()); + } + @Override public List findByScope(String... id) { DBObject query = new QueryBuilder() - .and("scopes.id").in(id) + .and("scopes").in(id) .get(); return getMongoStore().loadEntities(ResourceEntity.class, query, getInvocationContext()).stream() diff --git a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java index e57b69bf6e..4b7edd6a2f 100644 --- a/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java +++ b/model/mongo/src/main/java/org/keycloak/authorization/mongo/store/MongoScopeStore.java @@ -18,6 +18,7 @@ package org.keycloak.authorization.mongo.store; +import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.keycloak.authorization.AuthorizationProvider; @@ -31,6 +32,8 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.utils.KeycloakModelUtils; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import static java.util.stream.Collectors.toList; @@ -98,6 +101,21 @@ public class MongoScopeStore implements ScopeStore { .collect(toList()); } + @Override + public List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult) { + QueryBuilder queryBuilder = new QueryBuilder() + .and("resourceServerId").is(resourceServerId); + + attributes.forEach((name, value) -> { + queryBuilder.and(name).regex(Pattern.compile(".*" + value[0] + ".*", Pattern.CASE_INSENSITIVE)); + }); + + DBObject sort = new BasicDBObject("name", 1); + + return getMongoStore().loadEntities(ScopeEntity.class, queryBuilder.get(), sort, firstResult, maxResult, invocationContext).stream() + .map(scope -> findById(scope.getId())).collect(toList()); + } + private MongoStoreInvocationContext getInvocationContext() { return this.invocationContext; } diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java index f55db9971e..51c7dd71ec 100644 --- a/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java +++ b/server-spi/src/main/java/org/keycloak/authorization/store/PolicyStore.java @@ -19,11 +19,10 @@ package org.keycloak.authorization.store; import org.keycloak.authorization.model.Policy; -import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; import java.util.List; +import java.util.Map; /** * A {@link PolicyStore} is responsible to manage the persistence of {@link Policy} instances. @@ -75,6 +74,15 @@ public interface PolicyStore { */ List findByResourceServer(String resourceServerId); + /** + * Returns a list of {@link Policy} associated with a {@link ResourceServer} with the given resourceServerId. + * + * @param attributes a map holding the attributes that will be used as a filter + * @param resourceServerId the identifier of a resource server + * @return a list of policies that belong to the given resource server + */ + List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult); + /** * Returns a list of {@link Policy} associated with a {@link org.keycloak.authorization.core.model.Resource} with the given resourceId. * diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java index 5b92808b1e..f06be7853f 100644 --- a/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java +++ b/server-spi/src/main/java/org/keycloak/authorization/store/ResourceStore.java @@ -19,10 +19,9 @@ package org.keycloak.authorization.store; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.authorization.model.Scope; import java.util.List; -import java.util.Set; +import java.util.Map; /** * A {@link ResourceStore} is responsible to manage the persistence of {@link Resource} instances. @@ -72,6 +71,15 @@ public interface ResourceStore { */ List findByResourceServer(String resourceServerId); + /** + * Finds all {@link Resource} instances associated with a given resource server. + * + * @param attributes a map holding the attributes that will be used as a filter + * @param resourceServerId the identifier of the resource server + * @return a list with all resources associated with the given resource server + */ + List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult); + /** * Finds all {@link Resource} associated with a given scope. * diff --git a/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java b/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java index 501217f5e7..81a7064fc7 100644 --- a/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java +++ b/server-spi/src/main/java/org/keycloak/authorization/store/ScopeStore.java @@ -22,6 +22,7 @@ import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import java.util.List; +import java.util.Map; /** * A {@link ScopeStore} is responsible to manage the persistence of {@link Scope} instances. @@ -75,4 +76,14 @@ public interface ScopeStore { * @return a list of scopes that belong to the given resource server */ List findByResourceServer(String id); -} + + /** + * Returns a list of {@link Scope} associated with a {@link ResourceServer} with the given resourceServerId. + * + * @param attributes a map holding the attributes that will be used as a filter + * @param resourceServerId the identifier of a resource server + * + * @return a list of scopes that belong to the given resource server + */ + List findByResourceServer(Map attributes, String resourceServerId, int firstResult, int maxResult); +} \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java index d7e6dee2a4..3b9c1942c5 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyService.java @@ -26,8 +26,10 @@ import org.keycloak.authorization.policy.provider.PolicyProviderAdminService; import org.keycloak.authorization.policy.provider.PolicyProviderFactory; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.authorization.PolicyProviderRepresentation; import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.services.resources.admin.RealmAuth; import javax.ws.rs.Consumes; @@ -41,6 +43,12 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Collectors; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; @@ -188,11 +196,54 @@ public class PolicyService { @GET @Produces("application/json") @NoCache - public Response findAll() { + public Response findAll(@QueryParam("name") String name, + @QueryParam("type") String type, + @QueryParam("resource") String resource, + @QueryParam("permission") Boolean permission, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResult) { this.auth.requireView(); + + Map search = new HashMap<>(); + + if (name != null && !"".equals(name.trim())) { + search.put("name", new String[] {name}); + } + + if (type != null && !"".equals(type.trim())) { + search.put("type", new String[] {type}); + } + StoreFactory storeFactory = authorization.getStoreFactory(); + + if (resource != null && !"".equals(resource.trim())) { + List policies = new ArrayList<>(); + HashMap resourceSearch = new HashMap<>(); + + resourceSearch.put("name", new String[] {resource}); + + storeFactory.getResourceStore().findByResourceServer(resourceSearch, resourceServer.getId(), -1, -1).forEach(resource1 -> { + ResourceRepresentation resourceRepresentation = ModelToRepresentation.toRepresentation(resource1, resourceServer, authorization); + resourceRepresentation.getPolicies().forEach(policyRepresentation -> { + Policy associated = storeFactory.getPolicyStore().findById(policyRepresentation.getId()); + policies.add(associated); + findAssociatedPolicies(associated, policies); + }); + }); + + if (policies.isEmpty()) { + return Response.ok(Collections.emptyList()).build(); + } + + search.put("id", policies.stream().map(Policy::getId).toArray(String[]::new)); + } + + if (permission != null) { + search.put("permission", new String[] {permission.toString()}); + } + return Response.ok( - storeFactory.getPolicyStore().findByResourceServer(resourceServer.getId()).stream() + storeFactory.getPolicyStore().findByResourceServer(search, resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : -1).stream() .map(policy -> toRepresentation(policy, authorization)) .collect(Collectors.toList())) .build(); @@ -244,4 +295,11 @@ public class PolicyService { return null; } + + private void findAssociatedPolicies(Policy policy, List policies) { + policy.getAssociatedPolicies().forEach(associated -> { + policies.add(associated); + findAssociatedPolicies(associated, policies); + }); + } } diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java index c628a82f3e..d31146f7d4 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceSetService.java @@ -22,9 +22,13 @@ import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.models.ClientModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.services.ErrorResponse; import org.keycloak.services.resources.admin.RealmAuth; @@ -40,7 +44,10 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; @@ -170,12 +177,63 @@ public class ResourceSetService { @GET @NoCache @Produces("application/json") - public Response findAll() { + public Response findAll(@QueryParam("name") String name, + @QueryParam("uri") String uri, + @QueryParam("owner") String owner, + @QueryParam("type") String type, + @QueryParam("scope") String scope, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResult) { requireView(); StoreFactory storeFactory = authorization.getStoreFactory(); + Map search = new HashMap<>(); + + if (name != null && !"".equals(name.trim())) { + search.put("name", new String[] {name}); + } + + if (uri != null && !"".equals(uri.trim())) { + search.put("uri", new String[] {uri}); + } + + if (owner != null && !"".equals(owner.trim())) { + RealmModel realm = authorization.getKeycloakSession().getContext().getRealm(); + ClientModel clientModel = realm.getClientByClientId(owner); + + if (clientModel != null) { + owner = clientModel.getId(); + } else { + UserModel user = authorization.getKeycloakSession().users().getUserByUsername(owner, realm); + + if (user != null) { + owner = user.getId(); + } + } + + search.put("owner", new String[] {owner}); + } + + if (type != null && !"".equals(type.trim())) { + search.put("type", new String[] {type}); + } + + if (scope != null && !"".equals(scope.trim())) { + HashMap scopeFilter = new HashMap<>(); + + scopeFilter.put("name", new String[] {scope}); + + List scopes = authorization.getStoreFactory().getScopeStore().findByResourceServer(scopeFilter, resourceServer.getId(), -1, -1); + + if (scopes.isEmpty()) { + return Response.ok(Collections.emptyList()).build(); + } + + search.put("scope", scopes.stream().map(Scope::getId).toArray(String[]::new)); + } + return Response.ok( - storeFactory.getResourceStore().findByResourceServer(this.resourceServer.getId()).stream() + storeFactory.getResourceStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : -1).stream() .map(resource -> toRepresentation(resource, this.resourceServer, authorization)) .collect(Collectors.toList())) .build(); diff --git a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java index df0e6daee4..44464b4fe9 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ScopeService.java @@ -41,7 +41,9 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation; @@ -158,10 +160,19 @@ public class ScopeService { @GET @Produces("application/json") - public Response findAll() { + public Response findAll(@QueryParam("name") String name, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResult) { this.auth.requireView(); + + Map search = new HashMap<>(); + + if (name != null && !"".equals(name.trim())) { + search.put("name", new String[] {name}); + } + return Response.ok( - this.authorization.getStoreFactory().getScopeStore().findByResourceServer(this.resourceServer.getId()).stream() + this.authorization.getStoreFactory().getScopeStore().findByResourceServer(search, this.resourceServer.getId(), firstResult != null ? firstResult : -1, maxResult != null ? maxResult : -1).stream() .map(scope -> toRepresentation(scope, this.authorization)) .collect(Collectors.toList())) .build(); diff --git a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java index 168d62ba6e..b02a935238 100644 --- a/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java +++ b/services/src/main/java/org/keycloak/authorization/protection/resource/ResourceService.java @@ -109,7 +109,7 @@ public class ResourceService { } private Set findAll() { - Response response = this.resourceManager.findAll(); + Response response = this.resourceManager.findAll(null, null, null, null, null, -1, -1); List resources = (List) response.getEntity(); return resources.stream().map(ResourceRepresentation::getId).collect(Collectors.toSet()); } diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index ec37854c9c..9c67740e61 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -83,6 +83,13 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $scope.realm = realm; $scope.client = client; + $scope.query = { + realm: realm.realm, + client : client.id, + max : 20, + first : 0 + }; + ResourceServer.get({ realm : $route.current.params.realm, client : client.id @@ -93,10 +100,35 @@ module.controller('ResourceServerResourceCtrl', function($scope, $http, $route, $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/resource/create').search({rsrid: resource._id}); } - ResourceServerResource.query({realm : realm.realm, client : client.id}, function (data) { - $scope.resources = data; - }); + $scope.searchQuery(); }); + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + $scope.resources = ResourceServerResource.query($scope.query, function() { + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; }); module.controller('ResourceServerResourceDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerResource, ResourceServerScope, AuthzDialog, Notifications) { @@ -234,20 +266,52 @@ module.controller('ResourceServerScopeCtrl', function($scope, $http, $route, $lo $scope.realm = realm; $scope.client = client; + $scope.query = { + realm: realm.realm, + client : client.id, + max : 20, + first : 0 + }; + ResourceServer.get({ realm : $route.current.params.realm, client : client.id }, function(data) { $scope.server = data; - ResourceServerScope.query({realm : realm.realm, client : client.id}, function (data) { - $scope.scopes = data; - }); - $scope.createPolicy = function(scope) { $location.path('/realms/' + $route.current.params.realm + '/clients/' + client.id + '/authz/resource-server/permission/scope/create').search({scpid: scope.id}); } + + $scope.searchQuery(); }); + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + $scope.scopes = ResourceServerScope.query($scope.query, function() { + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; }); module.controller('ResourceServerScopeDetailCtrl', function($scope, $http, $route, $location, realm, ResourceServer, client, ResourceServerScope, AuthzDialog, Notifications) { @@ -364,6 +428,14 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l $scope.client = client; $scope.policyProviders = []; + $scope.query = { + realm: realm.realm, + client : client.id, + permission: false, + max : 20, + first : 0 + }; + PolicyProvider.query({ realm : $route.current.params.realm, client : client.id @@ -380,8 +452,35 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l client : client.id }, function(data) { $scope.server = data; + $scope.searchQuery(); + }); - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { + $scope.addPolicy = function(policyType) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create"); + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + ResourceServerPolicy.query($scope.query, function(data) { $scope.policies = []; for (i = 0; i < data.length; i++) { @@ -389,12 +488,11 @@ module.controller('ResourceServerPolicyCtrl', function($scope, $http, $route, $l $scope.policies.push(data[i]); } } - }); - }); - $scope.addPolicy = function(policyType) { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/policy/" + policyType.type + "/create"); - } + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; }); module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route, $location, realm, ResourceServer, ResourceServerPolicy, PolicyProvider, client) { @@ -402,6 +500,14 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route $scope.client = client; $scope.policyProviders = []; + $scope.query = { + realm: realm.realm, + client : client.id, + permission: true, + max : 20, + first : 0 + }; + PolicyProvider.query({ realm : $route.current.params.realm, client : client.id @@ -418,8 +524,35 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route client : client.id }, function(data) { $scope.server = data; + $scope.searchQuery(); + }); - ResourceServerPolicy.query({realm : realm.realm, client : client.id}, function (data) { + $scope.addPolicy = function(policyType) { + $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/" + policyType.type + "/create"); + } + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + $scope.searchLoaded = false; + + ResourceServerPolicy.query($scope.query, function(data) { $scope.policies = []; for (i = 0; i < data.length; i++) { @@ -427,12 +560,11 @@ module.controller('ResourceServerPermissionCtrl', function($scope, $http, $route $scope.policies.push(data[i]); } } - }); - }); - $scope.addPolicy = function(policyType) { - $location.url("/realms/" + realm.realm + "/clients/" + client.id + "/authz/resource-server/permission/" + policyType.type + "/create"); - } + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; }); module.controller('ResourceServerPolicyDroolsDetailCtrl', function($scope, $http, $route, realm, client, PolicyController) { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html index a41fe181ca..bfd1b22464 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/permission/resource-server-permission-list.html @@ -10,12 +10,21 @@
{{:: 'filter' | translate}}:  
- + +
+ +
- +
+ +
+
+
+
@@ -36,6 +45,17 @@ {{:: 'authz-associated-policies' | translate}} + + + +
+ + + +
+ + + {{policy.name}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html index 34240b6f9c..84600c097c 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/resource-server-policy-list.html @@ -10,12 +10,21 @@
{{:: 'filter' | translate}}:  
- + +
+ +
- +
+ +
+
+
+
@@ -35,6 +44,17 @@ {{:: 'type' | translate}} + + + +
+ + + +
+ + + {{policy.name}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html index 40b2cf9a4f..b1c3978988 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-resource-list.html @@ -10,22 +10,34 @@ {{:: 'filter' | translate}}:  
- +
- +
- +
- +
- + +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
@@ -45,6 +57,17 @@ {{:: 'actions' | translate}} + + + +
+ + + +
+ + + {{resource.name}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html index 39cfc776f3..78861293c9 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/resource-server-scope-list.html @@ -9,13 +9,12 @@
- +
- +
- @@ -29,6 +28,17 @@ {{:: 'actions' | translate}} + + + +
+ + + +
+ + + {{scope.name}} From 365a1b66c8b3324de292e367ffcb400eeda7c6c2 Mon Sep 17 00:00:00 2001 From: Vaclav Muzikar Date: Fri, 19 Aug 2016 11:40:01 +0200 Subject: [PATCH 11/49] KEYCLOAK-3448 Stabilize Internationalization UI Test --- .../tests/other/console/pom.xml | 23 ++++++++++++- .../account/messages/messages_cs.properties | 5 +++ .../account/messages/messages_en.properties | 2 ++ .../internat-test/account/theme.properties | 2 ++ .../messages/admin-messages_cs.properties | 7 ++++ .../admin/messages/messages_cs.properties | 2 ++ .../internat-test/admin/theme.properties | 2 ++ .../internat-test/email/theme.properties | 2 ++ .../login/messages/messages_cs.properties | 11 +++++++ .../login/messages/messages_en.properties | 2 ++ .../internat-test/login/theme.properties | 2 ++ .../realm/InternationalizationTest.java | 33 ++++++++++++++----- .../tests/other/pom.xml | 2 +- .../integration-arquillian/tests/pom.xml | 2 +- 14 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/messages/messages_cs.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/messages/messages_en.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/theme.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/messages/admin-messages_cs.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/messages/messages_cs.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/theme.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/email/theme.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/messages/messages_cs.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/messages/messages_en.properties create mode 100644 testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/theme.properties diff --git a/testsuite/integration-arquillian/tests/other/console/pom.xml b/testsuite/integration-arquillian/tests/other/console/pom.xml index d9232e8c0c..2459c6d98d 100644 --- a/testsuite/integration-arquillian/tests/other/console/pom.xml +++ b/testsuite/integration-arquillian/tests/other/console/pom.xml @@ -36,6 +36,26 @@ + + + org.codehaus.mojo + properties-maven-plugin + 1.0.0 + + + process-resources + + read-project-properties + + + + ${testsuite.constants} + + + + + + @@ -48,10 +68,11 @@ copy-resources - ${auth.server.home}/themes + ${keycloak.theme.dir} src/main/resources/themes + true diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/messages/messages_cs.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/messages/messages_cs.properties new file mode 100644 index 0000000000..f2fd7e02cd --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/messages/messages_cs.properties @@ -0,0 +1,5 @@ +#encoding: utf-8 +doSave=Uložit +doCancel=Zrušit +editAccountHtmlTitle=Upravit účet +locale_cs=Čeština diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/messages/messages_en.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/messages/messages_en.properties new file mode 100644 index 0000000000..cc26e68227 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/messages/messages_en.properties @@ -0,0 +1,2 @@ +#encoding: utf-8 +locale_cs=Čeština diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/theme.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/theme.properties new file mode 100644 index 0000000000..7bdb8fe5ec --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/account/theme.properties @@ -0,0 +1,2 @@ +parent=${theme-default-name} +locales=en,cs diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/messages/admin-messages_cs.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/messages/admin-messages_cs.properties new file mode 100644 index 0000000000..9a9ef0e171 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/messages/admin-messages_cs.properties @@ -0,0 +1,7 @@ +#encoding: utf-8 +enabled=Povolit +save=Uložit +cancel=Zrušit +onText=ZAP +offText=VYP +realm-settings=Nastavení Realmu diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/messages/messages_cs.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/messages/messages_cs.properties new file mode 100644 index 0000000000..e03207609e --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/messages/messages_cs.properties @@ -0,0 +1,2 @@ +#encoding: utf-8 +invalidPasswordMinLengthMessage=Neplatné heslo: musí mít alespoň {0} znaků. diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/theme.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/theme.properties new file mode 100644 index 0000000000..7bdb8fe5ec --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/admin/theme.properties @@ -0,0 +1,2 @@ +parent=${theme-default-name} +locales=en,cs diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/email/theme.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/email/theme.properties new file mode 100644 index 0000000000..7bdb8fe5ec --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/email/theme.properties @@ -0,0 +1,2 @@ +parent=${theme-default-name} +locales=en,cs diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/messages/messages_cs.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/messages/messages_cs.properties new file mode 100644 index 0000000000..3c43a7bd92 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/messages/messages_cs.properties @@ -0,0 +1,11 @@ +#encoding: utf-8 +doLogIn=Přihlásit +doRegister=Registrovat se +doCancel=Zrušit +doSubmit=Odeslat +doForgotPassword=Zapomněli jste heslo? +locale_cs=Čeština +username=Uživatelské jméno +usernameOrEmail=Uživatelské jméno nebo email +email=Email +password=Heslo diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/messages/messages_en.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/messages/messages_en.properties new file mode 100644 index 0000000000..cc26e68227 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/messages/messages_en.properties @@ -0,0 +1,2 @@ +#encoding: utf-8 +locale_cs=Čeština diff --git a/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/theme.properties b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/theme.properties new file mode 100644 index 0000000000..7bdb8fe5ec --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/console/src/main/resources/themes/internat-test/login/theme.properties @@ -0,0 +1,2 @@ +parent=${theme-default-name} +locales=en,cs diff --git a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/InternationalizationTest.java b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/InternationalizationTest.java index 090288b453..97cb3086bc 100644 --- a/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/InternationalizationTest.java +++ b/testsuite/integration-arquillian/tests/other/console/src/test/java/org/keycloak/testsuite/console/realm/InternationalizationTest.java @@ -4,6 +4,7 @@ import org.jboss.arquillian.graphene.page.Page; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.testsuite.auth.page.AuthRealm; import org.keycloak.testsuite.console.page.fragment.Dropdown; import org.keycloak.testsuite.console.page.realm.ThemeSettings; @@ -17,6 +18,13 @@ import static org.keycloak.testsuite.util.URLAssert.*; * @author Vaclav Muzikar */ public class InternationalizationTest extends AbstractRealmTest { + private static final String THEME_NAME = "internat-test"; + private static final String LOCALE_CS_NAME = "Čeština"; + + private static final String LABEL_CS_PASSWORD = "Heslo"; + private static final String LABEL_CS_REALM_SETTINGS = "Nastavení Realmu"; + private static final String LABEL_CS_EDIT_ACCOUNT = "Upravit účet"; + @Page private ThemeSettings themeSettingsPage; @@ -25,6 +33,13 @@ public class InternationalizationTest extends AbstractRealmTest { @Before public void beforeInternationalizationTest() { + RealmRepresentation realmRepresentation = testRealmResource().toRepresentation(); + realmRepresentation.setAccountTheme(THEME_NAME); + realmRepresentation.setAdminTheme(THEME_NAME); + realmRepresentation.setEmailTheme(THEME_NAME); + realmRepresentation.setLoginTheme(THEME_NAME); + testRealmResource().update(realmRepresentation); + realmSettingsPage.navigateTo(); tabs().themes(); themeSettingsPage.setInternatEnabled(true); @@ -47,14 +62,14 @@ public class InternationalizationTest extends AbstractRealmTest { public void loginInternationalization() { testRealmAdminConsolePage.navigateTo(); - localeDropdown.selectByText("Español"); - assertLocale(".//label[@for='password']", "Contraseña"); // Password + localeDropdown.selectByText(LOCALE_CS_NAME); + assertLocale(".//label[@for='password']", LABEL_CS_PASSWORD); loginToTestRealmConsoleAs(testUser); - assertConsoleLocale("Temas"); + assertConsoleLocale(LABEL_CS_REALM_SETTINGS); testRealmAccountPage.navigateTo(); - assertAccountLocale("Cuenta"); + assertAccountLocale(LABEL_CS_EDIT_ACCOUNT); } /** @@ -65,24 +80,24 @@ public class InternationalizationTest extends AbstractRealmTest { testRealmAccountPage.navigateTo(); loginPage.form().login(testUser); - localeDropdown.selectByText("Français"); + localeDropdown.selectByText(LOCALE_CS_NAME); testRealmAccountPage.navigateTo(); - assertAccountLocale("Compte"); + assertAccountLocale(LABEL_CS_EDIT_ACCOUNT); deleteAllCookiesForTestRealm(); loginToTestRealmConsoleAs(testUser); - assertConsoleLocale("Thèmes"); + assertConsoleLocale(LABEL_CS_REALM_SETTINGS); } private void assertConsoleLocale(String expected) { assertCurrentUrlEquals(realmSettingsPage); - assertLocale(".//a[contains(@href,'/theme-settings')]", expected); // Themes + assertLocale(".//div[@class='nav-category'][1]/ul/li[1]//a", expected); // Realm Settings } private void assertAccountLocale(String expected) { assertCurrentUrlEquals(testRealmAccountPage); - assertLocale(".//div[contains(@class,'bs-sidebar')]/ul/li", expected); // Account + assertLocale(".//div[contains(@class,'content-area')]/div[@class='row']/div/h2", expected); // Edit Account } private void assertLocale(String xpathSelector, String expected) { diff --git a/testsuite/integration-arquillian/tests/other/pom.xml b/testsuite/integration-arquillian/tests/other/pom.xml index bb238bf620..6b0f83c7f1 100644 --- a/testsuite/integration-arquillian/tests/other/pom.xml +++ b/testsuite/integration-arquillian/tests/other/pom.xml @@ -59,7 +59,7 @@ integration-arquillian-tests-base ${project.version} test-jar - arquillian.xml,keycloak-add-user.json,kerberos/* + arquillian.xml,keycloak-add-user.json,test-constants.properties,kerberos/* diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 6c250598a4..4bdc8238a2 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -74,7 +74,7 @@ true true - test-constants.properties + ${project.build.directory}/dependency/test-constants.properties From 6aafde7819f389fba490dfef2ca3823e4df72ea7 Mon Sep 17 00:00:00 2001 From: Ramunas Kraujutis Date: Sun, 21 Aug 2016 16:04:51 +0300 Subject: [PATCH 12/49] fixed translation for authz-policy-decision-strategy.tooltip changing "policy" to "permission" --- .../theme/base/admin/messages/admin-messages_en.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 02c7a7039f..125c9137a0 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1028,7 +1028,7 @@ authz-policy-logic.tooltip=The logic dictates how the policy decision should be authz-policy-apply-policy=Apply Policy authz-policy-apply-policy.tooltip=Specifies all the policies that must be applied to the scopes defined by this policy or permission. authz-policy-decision-strategy=Decision Strategy -authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given policy are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order to the overall decision be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order to the overall decision be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative. +authz-policy-decision-strategy.tooltip=The decision strategy dictates how the policies associated with a given permission are evaluated and how a final decision is obtained. 'Affirmative' means that at least one policy must evaluate to a positive decision in order to the overall decision be also positive. 'Unanimous' means that all policies must evaluate to a positive decision in order to the overall decision be also positive. 'Consensus' means that the number of positive decisions must be greater than the number of negative decisions. If the number of positive and negative is the same, the final decision will be negative. authz-policy-decision-strategy-affirmative=Affirmative authz-policy-decision-strategy-unanimous=Unanimous authz-policy-decision-strategy-consensus=Consensus From 93d65fa895360f1917dbe0c97c39cf256de000c8 Mon Sep 17 00:00:00 2001 From: Ramunas Kraujutis Date: Sun, 21 Aug 2016 16:09:18 +0300 Subject: [PATCH 13/49] fix select options and add values to fix issues when non-English locale is used with translated values --- .../provider/resource-server-policy-drools-detail.html | 8 ++++---- .../base/admin/resources/partials/client-clustering.html | 8 ++++---- .../admin/resources/partials/realm-events-config.html | 6 +++--- .../resources/partials/realm-identity-provider-oidc.html | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html index a121f17255..ba53e0b97e 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-drools-detail.html @@ -88,10 +88,10 @@
{{:: 'authz-policy-drools-update-period.tooltip' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html index 3b6c48cc97..4a6312b4d7 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-clustering.html @@ -19,10 +19,10 @@ max="31536000" data-ng-model="client.nodeReRegistrationTimeout" id="nodeReRegistrationTimeout" name="nodeReRegistrationTimeout"/>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html index 8f36f6a621..a798c72d53 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-events-config.html @@ -63,9 +63,9 @@
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html index 1db4c6af62..9d927efae0 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-oidc.html @@ -167,9 +167,9 @@ From c522a20ab9d29a8b8e2089057c1a956563e56640 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 22 Aug 2016 10:21:58 +0200 Subject: [PATCH 14/49] KEYCLOAK-3447 Manual upgrade of database schema --- .../configuration/keycloak-server.json | 4 +- .../DefaultJpaConnectionProviderFactory.java | 169 +++++++++++------- .../jpa/updater/JpaUpdaterProvider.java | 11 +- .../LiquibaseJpaUpdaterProvider.java | 55 ++++-- ...DefaultMongoConnectionFactoryProvider.java | 89 ++++----- .../java/org/keycloak/ServerStartupError.java | 48 +++++ .../resources/KeycloakApplication.java | 108 ++++++----- .../resources/META-INF/keycloak-server.json | 3 +- .../resources/META-INF/keycloak-server.json | 3 +- 9 files changed, 313 insertions(+), 177 deletions(-) create mode 100644 server-spi/src/main/java/org/keycloak/ServerStartupError.java diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json index b2f106792a..4097362834 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json @@ -60,7 +60,9 @@ "connectionsJpa": { "default": { "dataSource": "java:jboss/datasources/KeycloakDS", - "databaseSchema": "update" + "initializeEmpty": true, + "migrationStrategy": "update", + "migrationExport": "${jboss.home.dir}/keycloak-database-update.sql" } }, diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java index 4272fb8869..3c74264f8d 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java @@ -17,6 +17,7 @@ package org.keycloak.connections.jpa; +import java.io.File; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; @@ -42,6 +43,7 @@ import org.keycloak.models.dblock.DBLockProvider; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.provider.ServerInfoAwareProviderFactory; import org.keycloak.models.dblock.DBLockManager; +import org.keycloak.ServerStartupError; import org.keycloak.timer.TimerProvider; /** @@ -51,6 +53,10 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class); + enum MigrationStrategy { + UPDATE, VALIDATE, MANUAL + } + private volatile EntityManagerFactory emf; private Config.Scope config; @@ -125,22 +131,9 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema); } - - String databaseSchema; - String databaseSchemaConf = config.get("databaseSchema"); - if (databaseSchemaConf == null) { - throw new RuntimeException("Property 'databaseSchema' needs to be specified in the configuration"); - } - - if (databaseSchemaConf.equals("development-update")) { - properties.put("hibernate.hbm2ddl.auto", "update"); - databaseSchema = null; - } else if (databaseSchemaConf.equals("development-validate")) { - properties.put("hibernate.hbm2ddl.auto", "validate"); - databaseSchema = null; - } else { - databaseSchema = databaseSchemaConf; - } + MigrationStrategy migrationStrategy = getMigrationStrategy(); + boolean initializeEmpty = config.getBoolean("initializeEmpty", true); + File databaseUpdateFile = getDatabaseUpdateFile(); properties.put("hibernate.show_sql", config.getBoolean("showSql", false)); properties.put("hibernate.format_sql", config.getBoolean("formatSql", true)); @@ -153,39 +146,8 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide if (driverDialect != null) { properties.put("hibernate.dialect", driverDialect); } - - if (databaseSchema != null) { - logger.trace("Updating database"); - - JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class); - if (updater == null) { - throw new RuntimeException("Can't update database: JPA updater provider not found"); - } - // Check if having DBLock before trying to initialize hibernate - DBLockProvider dbLock = new DBLockManager(session).getDBLock(); - if (dbLock.hasLock()) { - updateOrValidateDB(databaseSchema, connection, updater, schema); - } else { - logger.trace("Don't have DBLock retrieved before upgrade. Needs to acquire lock first in separate transaction"); - - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() { - - @Override - public void run(KeycloakSession lockSession) { - DBLockManager dbLockManager = new DBLockManager(lockSession); - DBLockProvider dbLock2 = dbLockManager.getDBLock(); - dbLock2.waitForLock(); - try { - updateOrValidateDB(databaseSchema, connection, updater, schema); - } finally { - dbLock2.releaseLock(); - } - } - - }); - } - } + migration(migrationStrategy, initializeEmpty, schema, databaseUpdateFile, connection, session); int globalStatsInterval = config.getInt("globalStatsInterval", -1); if (globalStatsInterval != -1) { @@ -199,18 +161,6 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide if (globalStatsInterval != -1) { startGlobalStats(session, globalStatsInterval); } - - } catch (Exception e) { - // Safe rollback - if (connection != null) { - try { - connection.rollback(); - } catch (SQLException e2) { - logger.warn("Can't rollback connection", e2); - } - } - - throw e; } finally { // Close after creating EntityManagerFactory to prevent in-mem databases from closing if (connection != null) { @@ -226,6 +176,11 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide } } + private File getDatabaseUpdateFile() { + String databaseUpdateFile = config.get("migrationExport", "keycloak-database-update.sql"); + return new File(databaseUpdateFile); + } + protected void prepareOperationalInfo(Connection connection) { try { operationalInfo = new LinkedHashMap<>(); @@ -282,20 +237,82 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide timer.scheduleTask(new HibernateStatsReporter(emf), globalStatsIntervalSecs * 1000, "ReportHibernateGlobalStats"); } + public void migration(MigrationStrategy strategy, boolean initializeEmpty, String schema, File databaseUpdateFile, Connection connection, KeycloakSession session) { + JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class); - // Needs to be called with acquired DBLock - protected void updateOrValidateDB(String databaseSchema, Connection connection, JpaUpdaterProvider updater, String schema) { - if (databaseSchema.equals("update")) { - updater.update(connection, schema); - logger.trace("Database update completed"); - } else if (databaseSchema.equals("validate")) { - updater.validate(connection, schema); - logger.trace("Database validation completed"); + JpaUpdaterProvider.Status status = updater.validate(connection, schema); + if (status == JpaUpdaterProvider.Status.VALID) { + logger.debug("Database is up-to-date"); + } else if (status == JpaUpdaterProvider.Status.EMPTY) { + if (initializeEmpty) { + update(connection, schema, session, updater); + } else { + switch (strategy) { + case UPDATE: + update(connection, schema, session, updater); + break; + case MANUAL: + export(connection, schema, databaseUpdateFile, session, updater); + throw new ServerStartupError("Database not initialized, please initialize database with " + databaseUpdateFile.getAbsolutePath(), false); + case VALIDATE: + throw new ServerStartupError("Database not initialized, please enable database initialization", false); + } + } } else { - throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema); + switch (strategy) { + case UPDATE: + update(connection, schema, session, updater); + break; + case MANUAL: + export(connection, schema, databaseUpdateFile, session, updater); + throw new ServerStartupError("Database not up-to-date, please migrate database with " + databaseUpdateFile.getAbsolutePath(), false); + case VALIDATE: + throw new ServerStartupError("Database not up-to-date, please enable database migration", false); + } } } + protected void update(Connection connection, String schema, KeycloakSession session, JpaUpdaterProvider updater) { + DBLockProvider dbLock = new DBLockManager(session).getDBLock(); + if (dbLock.hasLock()) { + updater.update(connection, schema); + } else { + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() { + @Override + public void run(KeycloakSession lockSession) { + DBLockManager dbLockManager = new DBLockManager(lockSession); + DBLockProvider dbLock2 = dbLockManager.getDBLock(); + dbLock2.waitForLock(); + try { + updater.update(connection, schema); + } finally { + dbLock2.releaseLock(); + } + } + }); + } + } + + protected void export(Connection connection, String schema, File databaseUpdateFile, KeycloakSession session, JpaUpdaterProvider updater) { + DBLockProvider dbLock = new DBLockManager(session).getDBLock(); + if (dbLock.hasLock()) { + updater.export(connection, schema, databaseUpdateFile); + } else { + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() { + @Override + public void run(KeycloakSession lockSession) { + DBLockManager dbLockManager = new DBLockManager(lockSession); + DBLockProvider dbLock2 = dbLockManager.getDBLock(); + dbLock2.waitForLock(); + try { + updater.export(connection, schema, databaseUpdateFile); + } finally { + dbLock2.releaseLock(); + } + } + }); + } + } @Override public Connection getConnection() { @@ -323,4 +340,18 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide return operationalInfo; } + private MigrationStrategy getMigrationStrategy() { + String migrationStrategy = config.get("migrationStrategy"); + if (migrationStrategy == null) { + // Support 'databaseSchema' for backwards compatibility + migrationStrategy = config.get("databaseSchema"); + } + + if (migrationStrategy != null) { + return MigrationStrategy.valueOf(migrationStrategy.toUpperCase()); + } else { + return MigrationStrategy.UPDATE; + } + } + } diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java index b087535625..37228e3e52 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java @@ -19,6 +19,7 @@ package org.keycloak.connections.jpa.updater; import org.keycloak.provider.Provider; +import java.io.File; import java.sql.Connection; /** @@ -26,10 +27,14 @@ import java.sql.Connection; */ public interface JpaUpdaterProvider extends Provider { - public String FIRST_VERSION = "1.0.0.Final"; + enum Status { + VALID, EMPTY, OUTDATED + } - public void update(Connection connection, String defaultSchema); + void update(Connection connection, String defaultSchema); - public void validate(Connection connection, String defaultSchema); + Status validate(Connection connection, String defaultSchema); + + void export(Connection connection, String defaultSchema, File file); } diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java index 2610174ee5..b4a50a9c4d 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java @@ -30,6 +30,9 @@ import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionPr import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.models.KeycloakSession; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.lang.reflect.Method; import java.sql.Connection; import java.util.List; @@ -53,6 +56,15 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { @Override public void update(Connection connection, String defaultSchema) { + update(connection, null, defaultSchema); + } + + @Override + public void export(Connection connection, String defaultSchema, File file) { + update(connection, file, defaultSchema); + } + + private void update(Connection connection, File file, String defaultSchema) { logger.debug("Starting database update"); // Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks @@ -61,7 +73,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { try { // Run update with keycloak master changelog first Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema); - updateChangeSet(liquibase, liquibase.getChangeLogFile()); + updateChangeSet(liquibase, liquibase.getChangeLogFile(), file); // Run update for each custom JpaEntityProvider Set jpaProviders = session.getAllProviders(JpaEntityProvider.class); @@ -71,7 +83,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { String factoryId = jpaProvider.getFactoryId(); String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId); liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName); - updateChangeSet(liquibase, liquibase.getChangeLogFile()); + updateChangeSet(liquibase, liquibase.getChangeLogFile(), file); } } } catch (Exception e) { @@ -81,7 +93,8 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { } } - protected void updateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException { + + protected void updateChangeSet(Liquibase liquibase, String changelog, File exportFile) throws LiquibaseException, IOException { List changeSets = liquibase.listUnrunChangeSets((Contexts) null); if (!changeSets.isEmpty()) { List ranChangeSets = liquibase.getDatabase().getRanChangeSetList(); @@ -95,7 +108,12 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { } } - liquibase.update((Contexts) null); + if (exportFile != null) { + liquibase.update((Contexts) null, new FileWriter(exportFile)); + } else { + liquibase.update((Contexts) null); + } + logger.debugv("Completed database update for changelog {0}", changelog); } else { logger.debugv("Database is up to date for changelog {0}", changelog); @@ -107,13 +125,18 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { } @Override - public void validate(Connection connection, String defaultSchema) { + public Status validate(Connection connection, String defaultSchema) { logger.debug("Validating if database is updated"); + ThreadLocalSessionContext.setCurrentSession(session); try { // Validate with keycloak master changelog first Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema); - validateChangeSet(liquibase, liquibase.getChangeLogFile()); + + Status status = validateChangeSet(liquibase, liquibase.getChangeLogFile()); + if (status != Status.VALID) { + return status; + } // Validate each custom JpaEntityProvider Set jpaProviders = session.getAllProviders(JpaEntityProvider.class); @@ -123,24 +146,30 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { String factoryId = jpaProvider.getFactoryId(); String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId); liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName); - validateChangeSet(liquibase, liquibase.getChangeLogFile()); + if (validateChangeSet(liquibase, liquibase.getChangeLogFile()) != Status.VALID) { + return Status.OUTDATED; + } } } - } catch (LiquibaseException e) { throw new RuntimeException("Failed to validate database", e); } + + return Status.VALID; } - protected void validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException { + protected Status validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException { List changeSets = liquibase.listUnrunChangeSets((Contexts) null); if (!changeSets.isEmpty()) { - List ranChangeSets = liquibase.getDatabase().getRanChangeSetList(); - String errorMessage = String.format("Failed to validate database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update' or use other database. Used changelog was %s", - ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId(), changelog); - throw new RuntimeException(errorMessage); + if (changeSets.size() == liquibase.getDatabaseChangeLog().getChangeSets().size()) { + return Status.EMPTY; + } else { + logger.debugf("Validation failed. Database is not up-to-date for changelog %s", changelog); + return Status.OUTDATED; + } } else { logger.debugf("Validation passed. Database is up-to-date for changelog %s", changelog); + return Status.VALID; } } diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index b899e0d450..ce09046862 100755 --- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -51,6 +51,10 @@ import com.mongodb.ServerAddress; */ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory, ServerInfoAwareProviderFactory { + enum MigrationStrategy { + UPDATE, VALIDATE + } + // TODO Make it dynamic private String[] entities = new String[]{ "org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity", @@ -165,46 +169,34 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro } private void update(KeycloakSession session) { - String databaseSchema = config.get("databaseSchema"); + MigrationStrategy strategy = getMigrationStrategy(); - if (databaseSchema == null) { - throw new RuntimeException("Property 'databaseSchema' needs to be specified in the configuration of mongo connections"); + MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class); + if (mongoUpdater == null) { + throw new RuntimeException("Can't update database: Mongo updater provider not found"); + } + + DBLockProvider dbLock = new DBLockManager(session).getDBLock(); + if (dbLock.hasLock()) { + updateOrValidateDB(strategy, session, mongoUpdater); } else { - MongoUpdaterProvider mongoUpdater = session.getProvider(MongoUpdaterProvider.class); - if (mongoUpdater == null) { - throw new RuntimeException("Can't update database: Mongo updater provider not found"); - } + logger.trace("Don't have DBLock retrieved before upgrade. Needs to acquire lock first in separate transaction"); - DBLockProvider dbLock = new DBLockManager(session).getDBLock(); - if (dbLock.hasLock()) { - updateOrValidateDB(databaseSchema, session, mongoUpdater); - } else { - logger.trace("Don't have DBLock retrieved before upgrade. Needs to acquire lock first in separate transaction"); + KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() { - KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), new KeycloakSessionTask() { - - @Override - public void run(KeycloakSession lockSession) { - DBLockManager dbLockManager = new DBLockManager(lockSession); - DBLockProvider dbLock2 = dbLockManager.getDBLock(); - dbLock2.waitForLock(); - try { - updateOrValidateDB(databaseSchema, session, mongoUpdater); - } finally { - dbLock2.releaseLock(); - } + @Override + public void run(KeycloakSession lockSession) { + DBLockManager dbLockManager = new DBLockManager(lockSession); + DBLockProvider dbLock2 = dbLockManager.getDBLock(); + dbLock2.waitForLock(); + try { + updateOrValidateDB(strategy, session, mongoUpdater); + } finally { + dbLock2.releaseLock(); } + } - }); - } - - if (databaseSchema.equals("update")) { - mongoUpdater.update(session, db); - } else if (databaseSchema.equals("validate")) { - mongoUpdater.validate(session, db); - } else { - throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema); - } + }); } } @@ -217,13 +209,14 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro return entityClasses; } - protected void updateOrValidateDB(String databaseSchema, KeycloakSession session, MongoUpdaterProvider mongoUpdater) { - if (databaseSchema.equals("update")) { - mongoUpdater.update(session, db); - } else if (databaseSchema.equals("validate")) { - mongoUpdater.validate(session, db); - } else { - throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema); + protected void updateOrValidateDB(MigrationStrategy strategy, KeycloakSession session, MongoUpdaterProvider mongoUpdater) { + switch (strategy) { + case UPDATE: + mongoUpdater.update(session, db); + break; + case VALIDATE: + mongoUpdater.validate(session, db); + break; } } @@ -345,4 +338,18 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro return operationalInfo; } + private MigrationStrategy getMigrationStrategy() { + String migrationStrategy = config.get("migrationStrategy"); + if (migrationStrategy == null) { + // Support 'databaseSchema' for backwards compatibility + migrationStrategy = config.get("databaseSchema"); + } + + if (migrationStrategy != null) { + return MigrationStrategy.valueOf(migrationStrategy.toUpperCase()); + } else { + return MigrationStrategy.UPDATE; + } + } + } diff --git a/server-spi/src/main/java/org/keycloak/ServerStartupError.java b/server-spi/src/main/java/org/keycloak/ServerStartupError.java new file mode 100644 index 0000000000..967e66f66a --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/ServerStartupError.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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; + +/** + * Non-recoverable error thrown during server startup + * + * @author Stian Thorgersen + */ +public class ServerStartupError extends Error { + + private final boolean fillStackTrace; + + public ServerStartupError(String message) { + super(message); + fillStackTrace = true; + } + + public ServerStartupError(String message, boolean fillStackTrace) { + super(message); + this.fillStackTrace = fillStackTrace; + } + + @Override + public synchronized Throwable fillInStackTrace() { + if (fillStackTrace) { + return super.fillInStackTrace(); + } else { + return this; + } + } + +} diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 5e1cd9f876..dbfc99c371 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -72,69 +72,73 @@ public class KeycloakApplication extends Application { protected String contextPath; public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) { - loadConfig(); + try { + loadConfig(); - this.contextPath = context.getContextPath(); - this.sessionFactory = createSessionFactory(); + this.contextPath = context.getContextPath(); + this.sessionFactory = createSessionFactory(); - dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this); - ResteasyProviderFactory.pushContext(KeycloakApplication.class, this); // for injection - context.setAttribute(KeycloakSessionFactory.class.getName(), this.sessionFactory); + dispatcher.getDefaultContextObjects().put(KeycloakApplication.class, this); + ResteasyProviderFactory.pushContext(KeycloakApplication.class, this); // for injection + context.setAttribute(KeycloakSessionFactory.class.getName(), this.sessionFactory); - singletons.add(new ServerVersionResource()); - singletons.add(new RobotsResource()); - singletons.add(new RealmsResource()); - singletons.add(new AdminRoot()); - classes.add(ThemeResource.class); - classes.add(JsResource.class); + singletons.add(new ServerVersionResource()); + singletons.add(new RobotsResource()); + singletons.add(new RealmsResource()); + singletons.add(new AdminRoot()); + classes.add(ThemeResource.class); + classes.add(JsResource.class); - classes.add(KeycloakTransactionCommitter.class); + classes.add(KeycloakTransactionCommitter.class); - singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false")))); + singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false")))); - ExportImportManager[] exportImportManager = new ExportImportManager[1]; + ExportImportManager[] exportImportManager = new ExportImportManager[1]; - KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { + KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() { - @Override - public void run(KeycloakSession lockSession) { - DBLockManager dbLockManager = new DBLockManager(lockSession); - dbLockManager.checkForcedUnlock(); - DBLockProvider dbLock = dbLockManager.getDBLock(); - dbLock.waitForLock(); - try { - exportImportManager[0] = migrateAndBootstrap(); - } finally { - dbLock.releaseLock(); + @Override + public void run(KeycloakSession lockSession) { + DBLockManager dbLockManager = new DBLockManager(lockSession); + dbLockManager.checkForcedUnlock(); + DBLockProvider dbLock = dbLockManager.getDBLock(); + dbLock.waitForLock(); + try { + exportImportManager[0] = migrateAndBootstrap(); + } finally { + dbLock.releaseLock(); + } } + + }); + + + if (exportImportManager[0].isRunExport()) { + exportImportManager[0].runExport(); } - }); + boolean bootstrapAdminUser = false; + KeycloakSession session = sessionFactory.create(); + try { + session.getTransactionManager().begin(); + bootstrapAdminUser = new ApplianceBootstrap(session).isNoMasterUser(); + session.getTransactionManager().commit(); + } finally { + session.close(); + } - if (exportImportManager[0].isRunExport()) { - exportImportManager[0].runExport(); + sessionFactory.publish(new PostMigrationEvent()); + + singletons.add(new WelcomeResource(bootstrapAdminUser)); + + setupScheduledTasks(sessionFactory); + } catch (Throwable t) { + exit(1); + throw t; } - - boolean bootstrapAdminUser = false; - KeycloakSession session = sessionFactory.create(); - try { - session.getTransactionManager().begin(); - bootstrapAdminUser = new ApplianceBootstrap(session).isNoMasterUser(); - - session.getTransactionManager().commit(); - } finally { - session.close(); - } - - sessionFactory.publish(new PostMigrationEvent()); - - singletons.add(new WelcomeResource(bootstrapAdminUser)); - - setupScheduledTasks(sessionFactory); } - // Migrate model, bootstrap master realm, import realms and create admin user. This is done with acquired dbLock protected ExportImportManager migrateAndBootstrap() { ExportImportManager exportImportManager; @@ -185,7 +189,6 @@ public class KeycloakApplication extends Application { session.getTransactionManager().commit(); } catch (Exception e) { session.getTransactionManager().rollback(); - logger.migrationFailure(e); throw e; } finally { session.close(); @@ -386,4 +389,13 @@ public class KeycloakApplication extends Application { } } + private void exit(int status) { + new Thread() { + @Override + public void run() { + System.exit(status); + } + }.start(); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index 99e8614d0e..d8c4dc1b0c 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -90,7 +90,8 @@ "driverDialect": "${keycloak.connectionsJpa.driverDialect:}", "user": "${keycloak.connectionsJpa.user:sa}", "password": "${keycloak.connectionsJpa.password:}", - "databaseSchema": "${keycloak.connectionsJpa.databaseSchema:update}", + "initializeEmpty": true, + "migrationStrategy": "update", "showSql": "${keycloak.connectionsJpa.showSql:false}", "formatSql": "${keycloak.connectionsJpa.formatSql:true}", "globalStatsInterval": "${keycloak.connectionsJpa.globalStatsInterval:-1}" diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json index d3f87c98d1..b676b99c12 100755 --- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json @@ -65,7 +65,8 @@ "driverDialect": "${keycloak.connectionsJpa.driverDialect:}", "user": "${keycloak.connectionsJpa.user:sa}", "password": "${keycloak.connectionsJpa.password:}", - "databaseSchema": "${keycloak.connectionsJpa.databaseSchema:update}", + "initializeEmpty": true, + "migrationStrategy": "update", "showSql": "${keycloak.connectionsJpa.showSql:false}", "formatSql": "${keycloak.connectionsJpa.formatSql:true}", "globalStatsInterval": "${keycloak.connectionsJpa.globalStatsInterval:-1}" From 494d9973a6b51ce24b926b0b565ac2dc2847ece3 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 23 Aug 2016 14:57:31 +0200 Subject: [PATCH 15/49] KEYCLOAK-3460 Support for ClientUpdatedEvent --- .../org/keycloak/models/jpa/ClientAdapter.java | 12 ++++++++++++ .../mongo/keycloak/adapters/ClientAdapter.java | 15 +++++++++++++-- .../main/java/org/keycloak/models/RealmModel.java | 6 ++++++ .../models/utils/RepresentationToModel.java | 6 +++--- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java index 8e126e5698..e1a546e272 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java @@ -498,6 +498,18 @@ public class ClientAdapter implements ClientModel, JpaModel { @Override public void updateClient() { em.flush(); + session.getKeycloakSessionFactory().publish(new RealmModel.ClientUpdatedEvent() { + + @Override + public ClientModel getUpdatedClient() { + return ClientAdapter.this; + } + + @Override + public KeycloakSession getKeycloakSession() { + return session; + } + }); } @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java index f4983f98a3..5d6e0532d1 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java @@ -17,8 +17,6 @@ package org.keycloak.models.mongo.keycloak.adapters; -import com.mongodb.DBObject; -import com.mongodb.QueryBuilder; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientTemplateModel; @@ -65,6 +63,19 @@ public class ClientAdapter extends AbstractMongoAdapter imple @Override public void updateClient() { updateMongoEntity(); + + session.getKeycloakSessionFactory().publish(new RealmModel.ClientUpdatedEvent() { + + @Override + public ClientModel getUpdatedClient() { + return ClientAdapter.this; + } + + @Override + public KeycloakSession getKeycloakSession() { + return session; + } + }); } diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index 04f1476716..61dc9c10f8 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -56,6 +56,12 @@ public interface RealmModel extends RoleContainerModel { ClientModel getCreatedClient(); } + // Called also during client creation after client is fully initialized (including all attributes etc) + interface ClientUpdatedEvent extends ProviderEvent { + ClientModel getUpdatedClient(); + KeycloakSession getKeycloakSession(); + } + interface ClientRemovedEvent extends ProviderEvent { ClientModel getClient(); KeycloakSession getKeycloakSession(); diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index f36f010410..d1d5b059d7 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -943,7 +943,6 @@ public class RepresentationToModel { } else { client.setNodeReRegistrationTimeout(-1); } - client.updateClient(); if (resourceRep.getNotBefore() != null) { client.setNotBefore(resourceRep.getNotBefore()); @@ -1043,6 +1042,8 @@ public class RepresentationToModel { if (resourceRep.isUseTemplateMappers() != null) client.setUseTemplateMappers(resourceRep.isUseTemplateMappers()); else client.setUseTemplateMappers(resourceRep.getClientTemplate() != null); + client.updateClient(); + return client; } @@ -1066,7 +1067,6 @@ public class RepresentationToModel { if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired()); if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout()); if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType()); - resource.updateClient(); if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol()); if (rep.getAttributes() != null) { @@ -1122,7 +1122,7 @@ public class RepresentationToModel { } } - + resource.updateClient(); } // CLIENT TEMPLATES From 3493aa4ab7624f9c4ee585898bd33835d1683248 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Fri, 12 Aug 2016 15:04:42 -0400 Subject: [PATCH 16/49] KEYCLOAK-3196: Use WildFly management model for server configuration. --- .../demo-dist/src/main/xslt/standalone.xsl | 59 +++ .../server-feature-pack/assembly.xml | 11 - .../configuration/keycloak-server.json | 89 ---- .../keycloak-services/main/module.xml | 1 + .../main/module.xml | 2 + distribution/server-overlay/assembly.xml | 4 - .../cli/keycloak-install-ha.cli | 20 +- .../server-overlay/cli/keycloak-install.cli | 20 +- services/pom.xml | 5 + .../resources/KeycloakApplication.java | 30 +- .../keycloak/truststore/SSLSocketFactory.java | 2 +- .../messages/admin-messages_en.properties | 2 +- wildfly/server-subsystem/pom.xml | 8 + .../ModulesListAttributeBuilder.java | 32 ++ .../ProvidersListAttributeBuilder.java | 37 ++ .../server/extension/JsonConfigConverter.java | 262 ++++++++++ .../KeycloakAdapterConfigService.java | 131 ++++- .../server/extension/KeycloakExtension.java | 11 +- .../KeycloakServerDeploymentProcessor.java | 47 +- .../extension/KeycloakSubsystemAdd.java | 7 +- .../KeycloakSubsystemDefinition.java | 30 +- .../extension/KeycloakSubsystemParser.java | 212 ++++++++- ...eycloakSubsystemWriteAttributeHandler.java | 5 +- .../extension/MigrateJsonOperation.java | 97 ++++ .../extension/ProviderResourceAddHandler.java | 48 ++ .../extension/ProviderResourceDefinition.java | 69 +++ .../ProviderResourceRemoveHandler.java | 32 ++ .../extension/SpiResourceAddHandler.java | 47 ++ .../extension/SpiResourceDefinition.java | 59 +++ .../extension/SpiResourceRemoveHandler.java | 32 ++ .../extension/ThemeResourceAddHandler.java | 60 +++ .../extension/ThemeResourceDefinition.java | 135 ++++++ .../extension/ThemeResourceRemoveHandler.java | 32 ++ .../extension/LocalDescriptions.properties | 31 ++ .../schema/wildfly-keycloak-server_1_1.xsd | 97 +++- .../subsystem-templates/keycloak-server.xml | 97 +++- .../JsonConfigConverterTestCase.java | 446 ++++++++++++++++++ .../extension/SubsystemParsingTestCase.java | 16 +- .../server/extension/keycloak-server-1.1.xml | 89 +++- 39 files changed, 2225 insertions(+), 189 deletions(-) delete mode 100755 distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/attributes/ModulesListAttributeBuilder.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/attributes/ProvidersListAttributeBuilder.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/JsonConfigConverter.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/MigrateJsonOperation.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceAddHandler.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceDefinition.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceRemoveHandler.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceAddHandler.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceDefinition.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceRemoveHandler.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceAddHandler.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceDefinition.java create mode 100644 wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceRemoveHandler.java create mode 100644 wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/JsonConfigConverterTestCase.java diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl index 4ef3f0e3ea..db2247c43e 100755 --- a/distribution/demo-dist/src/main/xslt/standalone.xsl +++ b/distribution/demo-dist/src/main/xslt/standalone.xsl @@ -59,6 +59,65 @@ auth + + classpath:${jboss.home.dir}/providers/* + + master + 900 + + 2592000 + true + true + ${jboss.home.dir}/themes + + + jpa + + + + + + + + jpa + + + jpa + + + + + + jpa + + + jpa + + + basic + + + + + + + + + + + + + + + + + default + + + + + + diff --git a/distribution/feature-packs/server-feature-pack/assembly.xml b/distribution/feature-packs/server-feature-pack/assembly.xml index 41ef1737eb..861c698224 100644 --- a/distribution/feature-packs/server-feature-pack/assembly.xml +++ b/distribution/feature-packs/server-feature-pack/assembly.xml @@ -59,15 +59,4 @@ - - - - src/main/resources/content/standalone/configuration/keycloak-server.json - content/domain/servers/server-one/configuration - - - src/main/resources/content/standalone/configuration/keycloak-server.json - content/domain/servers/server-two/configuration - - diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json deleted file mode 100755 index 4097362834..0000000000 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "providers": [ - "classpath:${jboss.home.dir}/providers/*" - ], - - "admin": { - "realm": "master" - }, - - "eventsStore": { - "provider": "jpa", - "jpa": { - "exclude-events": [ "REFRESH_TOKEN" ] - } - }, - - "realm": { - "provider": "jpa" - }, - - "user": { - "provider": "jpa" - }, - - "userCache": { - "default" : { - "enabled": true - } - }, - - "userSessionPersister": { - "provider": "jpa" - }, - - "authorizationPersister": { - "provider": "jpa" - }, - - "timer": { - "provider": "basic" - }, - - "theme": { - "staticMaxAge": 2592000, - "cacheTemplates": true, - "cacheThemes": true, - "folder": { - "dir": "${jboss.home.dir}/themes" - } - }, - - "scheduled": { - "interval": 900 - }, - - "connectionsHttpClient": { - "default": {} - }, - - "connectionsJpa": { - "default": { - "dataSource": "java:jboss/datasources/KeycloakDS", - "initializeEmpty": true, - "migrationStrategy": "update", - "migrationExport": "${jboss.home.dir}/keycloak-database-update.sql" - } - }, - - "realmCache": { - "default" : { - "enabled": true - } - }, - - "connectionsInfinispan": { - "provider": "default", - "default": { - "cacheContainer" : "java:comp/env/infinispan/Keycloak" - } - }, - - "jta-lookup": { - "provider": "${keycloak.jta.lookup.provider:jboss}", - "jboss" : { - "enabled": true - } - - } -} \ No newline at end of file diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml index e769a7b265..de03ed8e8b 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml @@ -55,6 +55,7 @@ + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-server-subsystem/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-server-subsystem/main/module.xml index 9f44e5943b..d82e1a9dde 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-server-subsystem/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-server-subsystem/main/module.xml @@ -28,6 +28,8 @@ + + diff --git a/distribution/server-overlay/assembly.xml b/distribution/server-overlay/assembly.xml index 325cadff19..9240a1000b 100755 --- a/distribution/server-overlay/assembly.xml +++ b/distribution/server-overlay/assembly.xml @@ -84,10 +84,6 @@ - - ${project.build.directory}/unpacked/keycloak-${project.version}/standalone/configuration/keycloak-server.json - standalone/configuration - ${project.build.directory}/unpacked/keycloak-${project.version}/bin/add-user-keycloak.sh bin diff --git a/distribution/server-overlay/cli/keycloak-install-ha.cli b/distribution/server-overlay/cli/keycloak-install-ha.cli index a84a34aeb5..f52bbc050a 100644 --- a/distribution/server-overlay/cli/keycloak-install-ha.cli +++ b/distribution/server-overlay/cli/keycloak-install-ha.cli @@ -11,4 +11,22 @@ embed-server --server-config=standalone-ha.xml /subsystem=infinispan/cache-container=keycloak/distributed-cache=authorization:add(mode="SYNC",owners="1") /subsystem=infinispan/cache-container=keycloak/replicated-cache=work:add(mode="SYNC") /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) -/subsystem=keycloak-server:add(web-context=auth) \ No newline at end of file +/subsystem=keycloak-server:add(web-context=auth,master-realm-name=master,scheduled-task-interval=900,providers=[classpath:${jboss.home.dir}/providers/*]) +/subsystem=keycloak-server/theme=defaults/:add(dir=${jboss.home.dir}/themes,staticMaxAge=2592000,cacheTemplates=true,cacheThemes=true) +/subsystem=keycloak-server/spi=eventsStore/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true) +/subsystem=keycloak-server/spi=realm/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=user/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=userCache/:add +/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=userSessionPersister/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=authorizationPersister/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=timer/:add(default-provider=basic) +/subsystem=keycloak-server/spi=connectionsHttpClient/:add +/subsystem=keycloak-server/spi=connectionsHttpClient/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=connectionsJpa/:add +/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",databaseSchema => "update"},enabled=true) +/subsystem=keycloak-server/spi=realmCache/:add +/subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default) +/subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true) diff --git a/distribution/server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/cli/keycloak-install.cli index c29cd5f1e8..c3ca492b8a 100644 --- a/distribution/server-overlay/cli/keycloak-install.cli +++ b/distribution/server-overlay/cli/keycloak-install.cli @@ -11,4 +11,22 @@ embed-server --server-config=standalone.xml /subsystem=infinispan/cache-container=keycloak/local-cache=authorization:add() /subsystem=infinispan/cache-container=keycloak/local-cache=authorization/eviction=EVICTION:add(max-entries=100,strategy=LRU) /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) -/subsystem=keycloak-server:add(web-context=auth) \ No newline at end of file +/subsystem=keycloak-server:add(web-context=auth,master-realm-name=master,scheduled-task-interval=900,providers=[classpath:${jboss.home.dir}/providers/*]) +/subsystem=keycloak-server/theme=defaults/:add(dir=${jboss.home.dir}/themes,staticMaxAge=2592000,cacheTemplates=true,cacheThemes=true) +/subsystem=keycloak-server/spi=eventsStore/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true) +/subsystem=keycloak-server/spi=realm/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=user/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=userCache/:add +/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=userSessionPersister/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=authorizationPersister/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=timer/:add(default-provider=basic) +/subsystem=keycloak-server/spi=connectionsHttpClient/:add +/subsystem=keycloak-server/spi=connectionsHttpClient/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=connectionsJpa/:add +/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",databaseSchema => "update"},enabled=true) +/subsystem=keycloak-server/spi=realmCache/:add +/subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default) +/subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true) diff --git a/services/pom.xml b/services/pom.xml index a8a009dfec..57d79846db 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -72,6 +72,11 @@ twitter4j-core + + org.wildfly.core + wildfly-controller + provided + org.jboss.logging jboss-logging diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index dbfc99c371..b76ff546b5 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -56,12 +56,17 @@ import java.io.*; import java.net.URI; import java.net.URL; import java.util.*; +import org.jboss.dmr.ModelNode; /** * @author Bill Burke * @version $Revision: 1 $ */ public class KeycloakApplication extends Application { + // This param name is defined again in Keycloak Server Subsystem class + // org.keycloak.subsystem.server.extension.KeycloakServerDeploymentProcessor. We have this value in + // two places to avoid dependency between Keycloak Subsystem and Keycloak Services module. + public static final String KEYCLOAK_CONFIG_PARAM_NAME = "org.keycloak.server-subsystem.Config"; private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; @@ -73,7 +78,7 @@ public class KeycloakApplication extends Application { public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) { try { - loadConfig(); + loadConfig(context); this.contextPath = context.getContextPath(); this.sessionFactory = createSessionFactory(); @@ -209,12 +214,18 @@ public class KeycloakApplication extends Application { return uriInfo.getBaseUriBuilder().replacePath(getContextPath()).build(); } - public static void loadConfig() { + public static void loadConfig(ServletContext context) { try { JsonNode node = null; + + String dmrConfig = loadDmrConfig(context); + if (dmrConfig != null) { + node = new ObjectMapper().readTree(dmrConfig); + logger.loadingFrom("standalone.xml or domain.xml"); + } String configDir = System.getProperty("jboss.server.config.dir"); - if (configDir != null) { + if (node == null && configDir != null) { File f = new File(configDir + File.separator + "keycloak-server.json"); if (f.isFile()) { logger.loadingFrom(f.getAbsolutePath()); @@ -235,12 +246,23 @@ public class KeycloakApplication extends Application { Config.init(new JsonConfigProvider(node, properties)); return; } else { - throw new RuntimeException("Config 'keycloak-server.json' not found"); + throw new RuntimeException("Keycloak config not found."); } } catch (IOException e) { throw new RuntimeException("Failed to load config", e); } } + + private static String loadDmrConfig(ServletContext context) { + String dmrConfig = context.getInitParameter(KEYCLOAK_CONFIG_PARAM_NAME); + if (dmrConfig == null) return null; + + ModelNode dmrConfigNode = ModelNode.fromString(dmrConfig); + if (dmrConfigNode.asPropertyList().isEmpty()) return null; + + // note that we need to resolve expressions BEFORE we convert to JSON + return dmrConfigNode.resolve().toJSONString(true); + } public static KeycloakSessionFactory createSessionFactory() { DefaultKeycloakSessionFactory factory = new DefaultKeycloakSessionFactory(); diff --git a/services/src/main/java/org/keycloak/truststore/SSLSocketFactory.java b/services/src/main/java/org/keycloak/truststore/SSLSocketFactory.java index fd1574a04e..f7b5a1d217 100755 --- a/services/src/main/java/org/keycloak/truststore/SSLSocketFactory.java +++ b/services/src/main/java/org/keycloak/truststore/SSLSocketFactory.java @@ -29,7 +29,7 @@ import java.net.Socket; *

* This SSLSocketFactory can only use truststore configured by TruststoreProvider after the ProviderFactory was * initialized using standard Spi load / init mechanism. That will only happen if "truststore" provider is configured - * in keycloak-server.json. + * in standalone.xml or domain.xml. *

* If TruststoreProvider is not available this SSLSocketFactory will delegate all operations to javax.net.ssl.SSLSocketFactory.getDefault(). * diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 02c7a7039f..a753ae9175 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -684,7 +684,7 @@ ldap.custom-user-ldap-filter.tooltip=Additional LDAP Filter for filtering search search-scope=Search Scope ldap.search-scope.tooltip=For one level, we search for users just in DNs specified by User DNs. For subtree, we search in whole of their subtree. See LDAP documentation for more details use-truststore-spi=Use Truststore SPI -ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in keycloak-server.json. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if keycloak-server.json is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used. +ldap.use-truststore-spi.tooltip=Specifies whether LDAP connection will use the truststore SPI with the truststore configured in standalone.xml/domain.xml. 'Always' means that it will always use it. 'Never' means that it won't use it. 'Only for ldaps' means that it will use if your connection URL use ldaps. Note even if standalone.xml/domain.xml is not configured, the default Java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used. connection-pooling=Connection Pooling ldap.connection-pooling.tooltip=Does Keycloak should use connection pooling for accessing LDAP server ldap.pagination.tooltip=Does the LDAP server support pagination. diff --git a/wildfly/server-subsystem/pom.xml b/wildfly/server-subsystem/pom.xml index 5601739bdc..db0f3cac78 100755 --- a/wildfly/server-subsystem/pom.xml +++ b/wildfly/server-subsystem/pom.xml @@ -52,6 +52,14 @@ + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + org.wildfly.core wildfly-controller diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/attributes/ModulesListAttributeBuilder.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/attributes/ModulesListAttributeBuilder.java new file mode 100644 index 0000000000..d47ea1086b --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/attributes/ModulesListAttributeBuilder.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 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.server.attributes; + +import org.jboss.as.controller.StringListAttributeDefinition; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ModulesListAttributeBuilder extends StringListAttributeDefinition.Builder { + public ModulesListAttributeBuilder() { + super("modules"); + setAllowExpression(true); + setAllowNull(true); + } +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/attributes/ProvidersListAttributeBuilder.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/attributes/ProvidersListAttributeBuilder.java new file mode 100644 index 0000000000..2b4952e1fc --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/attributes/ProvidersListAttributeBuilder.java @@ -0,0 +1,37 @@ +/* + * Copyright 2016 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.server.attributes; + +import org.jboss.as.controller.StringListAttributeDefinition; +import org.jboss.dmr.ModelNode; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ProvidersListAttributeBuilder extends StringListAttributeDefinition.Builder { + public ProvidersListAttributeBuilder() { + super("providers"); + ModelNode provider = new ModelNode(); + provider.add("classpath:${jboss.home.dir}/providers/*"); + this.defaultValue = provider; + setAllowExpression(true); + setAllowNull(true); + } + +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/JsonConfigConverter.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/JsonConfigConverter.java new file mode 100644 index 0000000000..95010cc073 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/JsonConfigConverter.java @@ -0,0 +1,262 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.server.extension; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.operations.common.Util; +import org.jboss.dmr.ModelNode; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION; + +import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.PROVIDERS; +import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.MASTER_REALM_NAME; +import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.SCHEDULED_TASK_INTERVAL; +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.CACHE_TEMPLATES; +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.CACHE_THEMES; +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.DEFAULT; +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.DIR; +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.MODULES; +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.STATIC_MAX_AGE; +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.WELCOME_THEME; + +/** + * Converts json representation of Keycloak config to DMR operations. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class JsonConfigConverter { + + private static final List NON_SPI_LIST = new ArrayList(); + + static { + NON_SPI_LIST.add("providers"); + NON_SPI_LIST.add("admin"); + NON_SPI_LIST.add("theme"); + NON_SPI_LIST.add("scheduled"); + } + + /** + * Convert keycloak-server.json to DMR operations that write to standalone.xml + * or domain.xml. + * + * @param json The json representation of the config. + * @param subsysAddress The management model address of the keycloak-server subsystem. + * @return A list of DMR operations. + * @throws IOException If the json can not be parsed. + */ + public static List convertJsonConfig(String json, PathAddress subsysAddress) throws IOException { + List list = new ArrayList<>(); + + JsonNode root = new ObjectMapper().readTree(json); + + list.add(masterRealmName(root, subsysAddress)); + list.add(scheduledTaskInterval(root, subsysAddress)); + list.add(providers(root, subsysAddress)); + list.add(theme(root, subsysAddress.append(ThemeResourceDefinition.TAG_NAME, + ThemeResourceDefinition.RESOURCE_NAME))); + list.addAll(spis(root, subsysAddress)); + + return list; + } + + private static ModelNode masterRealmName(JsonNode root, PathAddress addr) { + JsonNode targetNode = getNode(root, "admin", "realm"); + String value = MASTER_REALM_NAME.getDefaultValue().asString(); + if (targetNode != null) value = targetNode.asText(value); + + ModelNode op = Util.createOperation(WRITE_ATTRIBUTE_OPERATION, addr); + op.get("name").set(MASTER_REALM_NAME.getName()); + op.get("value").set(value); + return op; + } + + private static ModelNode scheduledTaskInterval(JsonNode root, PathAddress addr) { + JsonNode targetNode = getNode(root, "scheduled", "interval"); + Long value = SCHEDULED_TASK_INTERVAL.getDefaultValue().asLong(); + if (targetNode != null) value = targetNode.asLong(value); + + ModelNode op = Util.createOperation(WRITE_ATTRIBUTE_OPERATION, addr); + op.get("name").set(SCHEDULED_TASK_INTERVAL.getName()); + op.get("value").set(value); + return op; + } + + private static ModelNode providers(JsonNode root, PathAddress addr) { + JsonNode targetNode = getNode(root, "providers"); + ModelNode value = PROVIDERS.getDefaultValue(); + if (targetNode != null && targetNode.isArray()) { + value = new ModelNode(); + for (JsonNode node : targetNode) { + value.add(node.asText()); + } + } + + ModelNode op = Util.createOperation(WRITE_ATTRIBUTE_OPERATION, addr); + op.get("name").set(PROVIDERS.getName()); + op.get("value").set(value); + return op; + } + + private static ModelNode theme(JsonNode root, PathAddress addr) { + JsonNode themeNode = getNode(root, "theme"); + ModelNode op = Util.createAddOperation(addr); + + JsonNode targetNode = getNode(themeNode, "staticMaxAge"); + Long lValue = STATIC_MAX_AGE.getDefaultValue().asLong(); + if (targetNode != null) lValue = targetNode.asLong(lValue); + op.get(STATIC_MAX_AGE.getName()).set(lValue); + + targetNode = getNode(themeNode, "cacheTemplates"); + Boolean bValue = CACHE_TEMPLATES.getDefaultValue().asBoolean(); + if (targetNode != null) bValue = targetNode.asBoolean(bValue); + op.get(CACHE_TEMPLATES.getName()).set(bValue); + + targetNode = getNode(themeNode, "cacheThemes"); + bValue = CACHE_THEMES.getDefaultValue().asBoolean(); + if (targetNode != null) bValue = targetNode.asBoolean(bValue); + op.get(CACHE_THEMES.getName()).set(bValue); + + targetNode = getNode(themeNode, "folder", "dir"); + String sValue = DIR.getDefaultValue().asString(); + if (targetNode != null) sValue = targetNode.asText(sValue); + op.get(DIR.getName()).set(sValue); + + targetNode = getNode(themeNode, "welcomeTheme"); + if (targetNode != null) op.get(WELCOME_THEME.getName()).set(targetNode.asText()); + + targetNode = getNode(themeNode, "default"); + if (targetNode != null) op.get(DEFAULT.getName()).set(targetNode.asText()); + + targetNode = getNode(themeNode, "module", "modules"); + if (targetNode != null && targetNode.isArray()) { + op.get(MODULES.getName()).set(themeModules(targetNode)); + } + + return op; + } + + private static ModelNode themeModules(JsonNode modulesNode) { + ModelNode modules = new ModelNode(); + for (JsonNode node : modulesNode) { + modules.add(node.asText()); + } + return modules; + } + + private static Collection spis(JsonNode root, PathAddress addr) { + List spis = new ArrayList<>(); + + Iterator spiIterator = root.fieldNames(); + while (spiIterator.hasNext()) { + String spiName = spiIterator.next(); + if (NON_SPI_LIST.contains(spiName)) continue; + + PathAddress spiAddr = addr.append("spi", spiName); + spis.addAll(spi(root, spiAddr, spiName)); + } + + return spis; + } + + private static List spi(JsonNode root, PathAddress spiAddr, String spiName) { + List spiAndProviders = new ArrayList<>(); + ModelNode op = Util.createAddOperation(spiAddr); + spiAndProviders.add(op); + + Iterator providerIterator = root.get(spiName).fieldNames(); + while (providerIterator.hasNext()) { + String providerName = providerIterator.next(); + if ("provider".equals(providerName)) { + op.get(SpiResourceDefinition.DEFAULT_PROVIDER.getName()).set(getNode(root, spiName, "provider").asText()); + } else { + PathAddress providerAddr = spiAddr.append("provider", providerName); + spiAndProviders.add(spiProvider(getNode(root, spiName, providerName), providerAddr)); + } + } + + return spiAndProviders; + } + + private static ModelNode spiProvider(JsonNode providerNode, PathAddress providerAddr) { + ModelNode op = Util.createAddOperation(providerAddr); + + ModelNode properties = new ModelNode(); + + Iterator propNames = providerNode.fieldNames(); + while (propNames.hasNext()) { + String propName = propNames.next(); + + if ("enabled".equals(propName)) { + op.get(ProviderResourceDefinition.ENABLED.getName()).set(providerNode.get(propName).asBoolean()); + } else { + if (providerNode.get(propName).isArray()) { + properties.get(propName).set(makeArrayText(providerNode.get(propName))); + } else { + properties.get(propName).set(providerNode.get(propName).asText()); + } + } + } + + if (properties.isDefined() && !properties.asPropertyList().isEmpty()) { + op.get("properties").set(properties); + } + + if (!op.hasDefined(ProviderResourceDefinition.ENABLED.getName())) { + op.get(ProviderResourceDefinition.ENABLED.getName()).set(ProviderResourceDefinition.ENABLED.getDefaultValue()); + } + + return op; + } + + private static String makeArrayText(JsonNode arrayNode) { + StringBuilder builder = new StringBuilder("["); + + Iterator nodes = arrayNode.iterator(); + while (nodes.hasNext()) { + JsonNode node = nodes.next(); + builder.append("\""); + builder.append(node.asText()); + builder.append("\""); + if (nodes.hasNext()) builder.append(","); + } + builder.append("]"); + + return builder.toString(); + } + + private static JsonNode getNode(JsonNode root, String... path) { + if (root == null) { + return null; + } + JsonNode n = root; + for (String p : path) { + n = n.get(p); + if (n == null) { + return null; + } + } + return n; + } + +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java index 03a2fe2251..0eec2eed0a 100755 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakAdapterConfigService.java @@ -16,9 +16,15 @@ */ package org.keycloak.subsystem.server.extension; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.dmr.ModelNode; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; +import org.jboss.dmr.Property; + /** * This service keeps track of the entire Keycloak management model so as to provide - * adapter configuration to each deployment at deploy time. + * configuration to the Keycloak Server. * * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. */ @@ -27,6 +33,8 @@ public final class KeycloakAdapterConfigService { static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService(); static final String DEPLOYMENT_NAME = "keycloak-server.war"; + + static ModelNode fullConfig = new ModelNode(); private String webContext; @@ -34,6 +42,127 @@ public final class KeycloakAdapterConfigService { private KeycloakAdapterConfigService() { } + void updateConfig(ModelNode operation, ModelNode config) { + PathAddress address = PathAddress.pathAddress(operation.get(ADDRESS)); + address = address.subAddress(1); // remove root (subsystem=keycloak-server) + + ModelNode newConfig = fullConfig.clone(); + ModelNode subNode = newConfig; + for (PathElement pathElement : address) { + subNode = subNode.get(pathElement.getKey(), pathElement.getValue()); + } + + subNode.set(config.clone()); + + // remove undefined properties + for (Property prop : subNode.asPropertyList()) { + if (!prop.getValue().isDefined()) { + subNode.remove(prop.getName()); + } + } + + fullConfig = newConfig; + } + + ModelNode getConfig() { + ModelNode copy = fullConfig.clone(); + //System.out.println("******** BEFORE *************"); + //System.out.println(copy); + //System.out.println("*****************************"); + copy.remove("web-context"); + massageScheduledTaskInterval(copy); + massageMasterRealm(copy); + massageTheme(copy); + massageSpis(copy); + //System.out.println("******** JSON *************"); + //System.out.println(copy.resolve().toJSONString(false)); + //System.out.println("**********************"); + return copy; + } + + // The "massage" methods rearrange the model so that everything will + // be where the Keycloak server's Config interface expects it to be. + + private void massageScheduledTaskInterval(ModelNode copy) { + if (!copy.hasDefined("scheduled-task-intervale")) return; + ModelNode taskInterval = copy.remove("scheduled-task-interval"); + copy.get("scheduled", "interval").set(taskInterval); + } + + private void massageMasterRealm(ModelNode copy) { + if (!copy.hasDefined("master-realm-name")) return; + ModelNode master = copy.remove("master-realm-name"); + copy.get("admin", "realm").set(master); + } + + private void massageTheme(ModelNode copy) { + if (!copy.hasDefined("theme")) return; + if (!copy.get("theme").hasDefined("defaults")) return; + + ModelNode themeDefaults = copy.get("theme", "defaults"); + copy.get("theme").set(themeDefaults); + + if (copy.has("theme", "dir")) { + ModelNode dir = copy.get("theme", "dir"); + copy.get("theme", "folder", "dir").set(dir); + copy.get("theme").remove("dir"); + } + + if (copy.has("theme", "modules")) { + ModelNode modules = copy.get("theme").remove("modules"); + copy.get("theme", "module", "modules").set(modules); + } + } + + private void massageSpis(ModelNode copy) { + if (!copy.hasDefined("spi")) return; + ModelNode spis = copy.remove("spi"); + + for (Property prop : spis.asPropertyList()) { + ModelNode spi = prop.getValue(); + + if (spi.has("provider")) { + massageProviders(spi); + } + + if (spi.has("default-provider")) { + ModelNode defaultProvider = spi.remove("default-provider"); + spi.get("provider").set(defaultProvider); + } + + copy.get(prop.getName()).set(spi); + } + } + + private void massageProviders(ModelNode spi) { + if (!spi.hasDefined("provider")) return; + ModelNode providers = spi.remove("provider"); + for (Property prop : providers.asPropertyList()) { + ModelNode provider = prop.getValue(); + if (provider.has("properties")) { + massageProviderProps(provider); + } + spi.get(prop.getName()).set(provider); + } + } + + private void massageProviderProps(ModelNode provider) { + if (!provider.hasDefined("properties")) return; + ModelNode providerProps = provider.remove("properties"); + for (Property prop : providerProps.asPropertyList()) { + ModelNode value = prop.getValue(); + if (isArray(value.asString().trim())) { + provider.get(prop.getName()).set(ModelNode.fromString(value.asString()).asList()); + } else { + provider.get(prop.getName()).set(value); + } + } + } + + private boolean isArray(String value) { + return value.startsWith("[") && value.endsWith("]"); + } + void setWebContext(String webContext) { this.webContext = webContext; } diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakExtension.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakExtension.java index c6a3cdb03a..bdfe47c8e7 100755 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakExtension.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakExtension.java @@ -26,6 +26,7 @@ import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver; import org.jboss.as.controller.parsing.ExtensionParsingContext; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; +import org.jboss.as.controller.registry.ManagementResourceRegistration; import static org.keycloak.subsystem.server.logging.KeycloakLogger.ROOT_LOGGER; @@ -44,6 +45,10 @@ public class KeycloakExtension implements Extension { private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition(); private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser(); private static final ModelVersion MGMT_API_VERSION = ModelVersion.create(1,1,0); + + static final ThemeResourceDefinition THEME_DEFINITION = new ThemeResourceDefinition(); + static final SpiResourceDefinition SPI_DEFINITION = new SpiResourceDefinition(); + static final ProviderResourceDefinition PROVIDER_DEFINITION = new ProviderResourceDefinition(); static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) { StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME); @@ -69,7 +74,11 @@ public class KeycloakExtension implements Extension { ROOT_LOGGER.debug("Activating Keycloak Extension"); final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MGMT_API_VERSION); - subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE); + ManagementResourceRegistration subsystemRegistration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE); + subsystemRegistration.registerSubModel(THEME_DEFINITION); + ManagementResourceRegistration spiRegistration = subsystemRegistration.registerSubModel(SPI_DEFINITION); + spiRegistration.registerSubModel(PROVIDER_DEFINITION); + subsystem.registerXMLElementWriter(PARSER); } } diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java index f99cab3cd7..1bc2211e24 100755 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakServerDeploymentProcessor.java @@ -16,41 +16,78 @@ */ package org.keycloak.subsystem.server.extension; +import java.util.ArrayList; +import java.util.List; import org.jboss.as.ee.component.EEModuleDescription; 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.metadata.javaee.spec.ParamValueMetaData; +import org.jboss.metadata.web.jboss.JBossWebMetaData; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.ServiceTarget; /** - * DUP responsible for setting the web context of a Keycloak auth server. + * DUP responsible for setting the web context of a Keycloak auth server and + * passing the Keycloak configuration to the Keycloak server. * * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. */ public class KeycloakServerDeploymentProcessor implements DeploymentUnitProcessor { + // This param name is defined again in Keycloak Services class + // org.keycloak.services.resources.KeycloakApplication. We have this value in + // two places to avoid dependency between Keycloak Subsystem and Keyclaok Services module. + public static final String KEYCLOAK_CONFIG_PARAM_NAME = "org.keycloak.server-subsystem.Config"; + private static final ServiceName cacheContainerService = ServiceName.of("jboss", "infinispan", "keycloak"); - + @Override public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); - KeycloakAdapterConfigService config = KeycloakAdapterConfigService.INSTANCE; + KeycloakAdapterConfigService configService = KeycloakAdapterConfigService.INSTANCE; String deploymentName = deploymentUnit.getName(); - if (!config.isKeycloakServerDeployment(deploymentName)) { + if (!configService.isKeycloakServerDeployment(deploymentName)) { return; } final EEModuleDescription description = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION); - String webContext = config.getWebContext(); + String webContext = configService.getWebContext(); if (webContext == null) { throw new DeploymentUnitProcessingException("Can't determine web context/module for Keycloak Server"); } description.setModuleName(webContext); addInfinispanCaches(phaseContext); + addConfiguration(deploymentUnit, configService); + } + + private void addConfiguration(DeploymentUnit deploymentUnit, KeycloakAdapterConfigService configService) throws DeploymentUnitProcessingException { + WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY); + if (warMetaData == null) { + throw new DeploymentUnitProcessingException("WarMetaData not found for KeycloakServer."); + } + + 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(KEYCLOAK_CONFIG_PARAM_NAME); + param.setParamValue(configService.getConfig().toString()); + contextParams.add(param); + + webMetaData.setContextParams(contextParams); } private void addInfinispanCaches(DeploymentPhaseContext context) { diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemAdd.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemAdd.java index 3c6dc34c80..6d75e28af6 100755 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemAdd.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemAdd.java @@ -62,9 +62,10 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler { }, OperationContext.Stage.RUNTIME); } - protected void populateModel(final OperationContext context, final ModelNode operation, final Resource resource) throws OperationFailedException { + @Override + protected void populateModel(final OperationContext context, final ModelNode operation, final Resource resource) throws OperationFailedException { ModelNode model = resource.getModel(); - + // set attribute values from parsed model for (AttributeDefinition attrDef : ALL_ATTRIBUTES) { attrDef.validateAndSet(operation, model); @@ -89,5 +90,7 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler { ServerUtil serverUtil = new ServerUtil(operation); serverUtil.addStepToUploadServerWar(context); KeycloakAdapterConfigService.INSTANCE.setWebContext(webContext); + + KeycloakAdapterConfigService.INSTANCE.updateConfig(operation, model); } } diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemDefinition.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemDefinition.java index 6fcee7b08a..3dc4917324 100644 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemDefinition.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemDefinition.java @@ -29,6 +29,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.jboss.as.controller.StringListAttributeDefinition; +import org.keycloak.subsystem.server.attributes.ProvidersListAttributeBuilder; /** * Definition of subsystem=keycloak-server. @@ -44,15 +46,34 @@ public class KeycloakSubsystemDefinition extends SimpleResourceDefinition { .setRestartAllServices() .build(); - static final List ALL_ATTRIBUTES = new ArrayList(); + static final StringListAttributeDefinition PROVIDERS = new ProvidersListAttributeBuilder().build(); + + static final SimpleAttributeDefinition MASTER_REALM_NAME = + new SimpleAttributeDefinitionBuilder("master-realm-name", ModelType.STRING, true) + .setAllowExpression(true) + .setDefaultValue(new ModelNode("master")) + .setRestartAllServices() + .build(); + + static final SimpleAttributeDefinition SCHEDULED_TASK_INTERVAL = + new SimpleAttributeDefinitionBuilder("scheduled-task-interval", ModelType.LONG, true) + .setAllowExpression(true) + .setDefaultValue(new ModelNode("900")) + .setRestartAllServices() + .build(); + + static final List ALL_ATTRIBUTES = new ArrayList(); static { ALL_ATTRIBUTES.add(WEB_CONTEXT); + ALL_ATTRIBUTES.add(PROVIDERS); + ALL_ATTRIBUTES.add(MASTER_REALM_NAME); + ALL_ATTRIBUTES.add(SCHEDULED_TASK_INTERVAL); } - private static final Map DEFINITION_LOOKUP = new HashMap(); + private static final Map DEFINITION_LOOKUP = new HashMap(); static { - for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { + for (AttributeDefinition def : ALL_ATTRIBUTES) { DEFINITION_LOOKUP.put(def.getXmlName(), def); } } @@ -71,6 +92,7 @@ public class KeycloakSubsystemDefinition extends SimpleResourceDefinition { public void registerOperations(ManagementResourceRegistration resourceRegistration) { super.registerOperations(resourceRegistration); resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + resourceRegistration.registerOperationHandler(MigrateJsonOperation.DEFINITION, new MigrateJsonOperation()); } @Override @@ -81,7 +103,7 @@ public class KeycloakSubsystemDefinition extends SimpleResourceDefinition { } } - public static SimpleAttributeDefinition lookup(String name) { + public static AttributeDefinition lookup(String name) { return DEFINITION_LOOKUP.get(name); } } diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemParser.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemParser.java index 6db6e5701e..0f4ea8c0aa 100755 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemParser.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/KeycloakSubsystemParser.java @@ -29,9 +29,26 @@ import org.jboss.staxmapper.XMLExtendedStreamWriter; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.XMLStreamException; import java.util.List; +import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.PropertiesAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.dmr.Property; import static org.keycloak.subsystem.server.extension.KeycloakExtension.PATH_SUBSYSTEM; import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.WEB_CONTEXT; +import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.PROVIDERS; +import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.MASTER_REALM_NAME; +import static org.keycloak.subsystem.server.extension.KeycloakSubsystemDefinition.SCHEDULED_TASK_INTERVAL; + +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.MODULES; + +import static org.keycloak.subsystem.server.extension.SpiResourceDefinition.DEFAULT_PROVIDER; + +import static org.keycloak.subsystem.server.extension.ProviderResourceDefinition.ENABLED; +import static org.keycloak.subsystem.server.extension.ProviderResourceDefinition.PROPERTIES; /** * The subsystem parser, which uses stax to read and write to and from xml @@ -51,12 +68,116 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader

  • list, final XMLExtendedStreamReader reader) throws XMLStreamException { + ModelNode addThemeDefaults = new ModelNode(); + addThemeDefaults.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + PathElement.pathElement(ThemeResourceDefinition.TAG_NAME, ThemeResourceDefinition.RESOURCE_NAME)); + addThemeDefaults.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + list.add(addThemeDefaults); + + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + String tagName = reader.getLocalName(); + if (MODULES.getName().equals(tagName)) { + readModules(reader, addThemeDefaults); + continue; + } + + SimpleAttributeDefinition def = KeycloakExtension.THEME_DEFINITION.lookup(tagName); + if (def == null) throw new XMLStreamException("Unknown theme tag " + tagName); + def.parseAndSetParameter(reader.getElementText(), addThemeDefaults, reader); + } + } + + private void readModules(final XMLExtendedStreamReader reader, ModelNode addThemeDefaults) throws XMLStreamException { + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + MODULES.parseAndAddParameterElement(reader.getElementText(),addThemeDefaults, reader); + } + } + + private void readSpi(final List list, final XMLExtendedStreamReader reader) throws XMLStreamException { + String spiName = ParseUtils.requireAttributes(reader, "name")[0]; + ModelNode addSpi = new ModelNode(); + addSpi.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + PathElement.pathElement(SpiResourceDefinition.TAG_NAME, spiName)); + addSpi.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + list.add(addSpi); + + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + if (reader.getLocalName().equals(DEFAULT_PROVIDER.getXmlName())) { + DEFAULT_PROVIDER.parseAndSetParameter(reader.getElementText(), addSpi, reader); + } else if (reader.getLocalName().equals(ProviderResourceDefinition.TAG_NAME)) { + readProvider(list, spiName, reader); + } + } + } + + private void readProvider(final List list, String spiName, final XMLExtendedStreamReader reader) throws XMLStreamException { + String[] attributes = ParseUtils.requireAttributes(reader, "name", ENABLED.getXmlName()); + String providerName = attributes[0]; + String enabled = attributes[1]; + + ModelNode addProvider = new ModelNode(); + addProvider.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + PathElement.pathElement(SpiResourceDefinition.TAG_NAME, spiName), + PathElement.pathElement(ProviderResourceDefinition.TAG_NAME, providerName)); + addProvider.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + addProvider.get(ENABLED.getName()).set(Boolean.valueOf(enabled)); + list.add(addProvider); + + while (nextTag(reader) != END_ELEMENT) { + if (reader.getLocalName().equals(PROPERTIES.getXmlName())) { + readProperties(PROPERTIES, addProvider, reader); + } + } + } + + private void readProperties(final PropertiesAttributeDefinition attrDef, ModelNode addOp, final XMLExtendedStreamReader reader) throws XMLStreamException { + while (nextTag(reader) != END_ELEMENT) { + int attrCount = reader.getAttributeCount(); + if (attrCount != 2) throw new XMLStreamException("Property must have only two attributes"); + String name = ""; + String value = ""; + for (int i=0 ; i < 2; i++) { + String attrName = reader.getAttributeLocalName(i); + String attrValue = reader.getAttributeValue(i); + if (attrName.equals("name")) { + name = attrValue; + } else if (attrName.equals("value")) { + value = attrValue; + } else { + throw new XMLStreamException("Property can only have attributes named 'name' and 'value'"); + } + } + attrDef.parseAndAddParameterElement(name, value, addOp, reader); + nextTag(reader); + } + } + // used for debugging private int nextTag(XMLExtendedStreamReader reader) throws XMLStreamException { return reader.nextTag(); @@ -69,9 +190,64 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • definitions) { + public KeycloakSubsystemWriteAttributeHandler(List definitions) { this(definitions.toArray(new AttributeDefinition[definitions.size()])); } @@ -59,7 +58,7 @@ public class KeycloakSubsystemWriteAttributeHandler extends ModelOnlyWriteAttrib } private boolean attribNotChanging(String attributeName, ModelNode newValue, ModelNode oldValue) { - SimpleAttributeDefinition attribDef = KeycloakSubsystemDefinition.lookup(attributeName); + AttributeDefinition attribDef = KeycloakSubsystemDefinition.lookup(attributeName); if (!oldValue.isDefined()) { oldValue = attribDef.getDefaultValue(); } diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/MigrateJsonOperation.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/MigrateJsonOperation.java new file mode 100644 index 0000000000..92c229ddc5 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/MigrateJsonOperation.java @@ -0,0 +1,97 @@ +/* + * Copyright 2016 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.server.extension; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationDefinition; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.OperationStepHandler; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleOperationDefinitionBuilder; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; + +/** + * This operation provides a migration path from keycloak-server.json to + * standalone.xml or domain.xml. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class MigrateJsonOperation implements OperationStepHandler { + public static final String OPERATION_NAME = "migrate-json"; + + private static final String CONFIG_DIR = System.getProperty("jboss.server.config.dir"); + private static final Path DEFAULT_CONFIG_FILE = Paths.get(CONFIG_DIR, "keycloak-server.json"); + + private static final AttributeDefinition FILE_ATTRIBUTE = SimpleAttributeDefinitionBuilder.create("file", ModelType.BYTES, true).build(); + public static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, KeycloakExtension.getResourceDescriptionResolver()) + .setRuntimeOnly() + .setReadOnly() + .setReplyType(ModelType.STRING) + .setParameters(FILE_ATTRIBUTE) + .build(); + + private String localConfig() throws IOException { + if (Files.notExists(DEFAULT_CONFIG_FILE)) return null; + return new String(Files.readAllBytes(DEFAULT_CONFIG_FILE)); + } + + private String readConfig(ModelNode operation) throws IOException { + ModelNode file = operation.get(FILE_ATTRIBUTE.getName()); + if (file.isDefined() && file.asBytes().length > 0) { + return new String(file.asBytes()); + } + + String localConfig = localConfig(); + if (localConfig != null) return localConfig; + + throw new IOException("Can not find json file to migrate"); + } + + @Override + public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { + List ops = null; + try { + PathAddress currentAddr = context.getCurrentAddress(); + ops = JsonConfigConverter.convertJsonConfig(readConfig(operation), currentAddr); + } catch (IOException ioe) { + throw new OperationFailedException(ioe); + } + + for (ModelNode op : ops) { + PathAddress addr = PathAddress.pathAddress(op.get(ADDRESS)); + String opName = op.get(OP).asString(); + context.addStep(op, + context.getRootResourceRegistration().getOperationHandler(addr, opName), + OperationContext.Stage.MODEL); + } + + context.completeStep(OperationContext.RollbackHandler.NOOP_ROLLBACK_HANDLER); + } + +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceAddHandler.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceAddHandler.java new file mode 100644 index 0000000000..7db883c053 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceAddHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2016 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.server.extension; + +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.OperationFailedException; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import org.jboss.dmr.ModelNode; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ProviderResourceAddHandler extends AbstractAddStepHandler { + + public static ProviderResourceAddHandler INSTANCE = new ProviderResourceAddHandler(); + + private ProviderResourceAddHandler() { + } + + @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 SPI. operation=" + operation.toString()); + } + + ProviderResourceDefinition.ENABLED.validateAndSet(operation, model); + ProviderResourceDefinition.PROPERTIES.validateAndSet(operation, model); + + KeycloakAdapterConfigService.INSTANCE.updateConfig(operation, model); + } +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceDefinition.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceDefinition.java new file mode 100644 index 0000000000..0a4bf36845 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceDefinition.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016 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.server.extension; + +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.PropertiesAttributeDefinition; +import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ProviderResourceDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "provider"; + + protected static final SimpleAttributeDefinition ENABLED = + new SimpleAttributeDefinitionBuilder("enabled", ModelType.BOOLEAN, true) + .setAllowExpression(true) + .setDefaultValue(new ModelNode(true)) + .setAllowNull(false) + .setRestartAllServices() + .build(); + + static final PropertiesAttributeDefinition PROPERTIES = + new PropertiesAttributeDefinition.Builder("properties", true) + .setRestartAllServices() + .build(); + + protected static final ReloadRequiredWriteAttributeHandler WRITE_ATTR_HANDLER = new ReloadRequiredWriteAttributeHandler(ENABLED); + + protected ProviderResourceDefinition() { + super(PathElement.pathElement(TAG_NAME), + KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + ProviderResourceAddHandler.INSTANCE, + ProviderResourceRemoveHandler.INSTANCE + ); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + resourceRegistration.registerReadWriteAttribute(ENABLED, null, WRITE_ATTR_HANDLER); + resourceRegistration.registerReadWriteAttribute(PROPERTIES, null, WRITE_ATTR_HANDLER); + } + + +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceRemoveHandler.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceRemoveHandler.java new file mode 100644 index 0000000000..21d8217410 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceRemoveHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 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.server.extension; + +import org.jboss.as.controller.AbstractRemoveStepHandler; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ProviderResourceRemoveHandler extends AbstractRemoveStepHandler { + + public static ProviderResourceRemoveHandler INSTANCE = new ProviderResourceRemoveHandler(); + + private ProviderResourceRemoveHandler() {} + +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceAddHandler.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceAddHandler.java new file mode 100644 index 0000000000..3d417917e8 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceAddHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016 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.server.extension; + +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.OperationFailedException; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import org.jboss.dmr.ModelNode; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class SpiResourceAddHandler extends AbstractAddStepHandler { + + public static SpiResourceAddHandler INSTANCE = new SpiResourceAddHandler(); + + private SpiResourceAddHandler() {} + + @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 SPI. operation=" + operation.toString()); + } + + SpiResourceDefinition.DEFAULT_PROVIDER.validateAndSet(operation, model); + + KeycloakAdapterConfigService.INSTANCE.updateConfig(operation, model); + } +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceDefinition.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceDefinition.java new file mode 100644 index 0000000000..db15ba4911 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceDefinition.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 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.server.extension; + +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.dmr.ModelType; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class SpiResourceDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "spi"; + + protected static final SimpleAttributeDefinition DEFAULT_PROVIDER = + new SimpleAttributeDefinitionBuilder("default-provider", ModelType.STRING, true) + .setAllowExpression(true) + .setRestartAllServices() + .build(); + + protected static final ReloadRequiredWriteAttributeHandler WRITE_ATTR_HANDLER = new ReloadRequiredWriteAttributeHandler(DEFAULT_PROVIDER); + + protected SpiResourceDefinition() { + super(PathElement.pathElement(TAG_NAME), + KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + SpiResourceAddHandler.INSTANCE, + SpiResourceRemoveHandler.INSTANCE + ); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + resourceRegistration.registerReadWriteAttribute(DEFAULT_PROVIDER, null, WRITE_ATTR_HANDLER); + } + + +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceRemoveHandler.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceRemoveHandler.java new file mode 100644 index 0000000000..468a40a6a6 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/SpiResourceRemoveHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 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.server.extension; + +import org.jboss.as.controller.AbstractRemoveStepHandler; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class SpiResourceRemoveHandler extends AbstractRemoveStepHandler { + + public static SpiResourceRemoveHandler INSTANCE = new SpiResourceRemoveHandler(); + + private SpiResourceRemoveHandler() {} + +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceAddHandler.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceAddHandler.java new file mode 100644 index 0000000000..b955101b47 --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceAddHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 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.server.extension; + +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import org.jboss.dmr.ModelNode; +import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.ALL_ATTRIBUTES; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ThemeResourceAddHandler extends AbstractAddStepHandler { + + public static ThemeResourceAddHandler INSTANCE = new ThemeResourceAddHandler(); + + private ThemeResourceAddHandler() {} + + @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 Theme. operation=" + operation.toString()); + } + + PathAddress address = PathAddress.pathAddress(operation.get(ADDRESS)); + PathElement last = address.getLastElement(); + if (!last.getValue().equals(ThemeResourceDefinition.RESOURCE_NAME)) { + throw new OperationFailedException("Theme resource with name " + last.getValue() + " not allowed."); + } + + for (AttributeDefinition def : ALL_ATTRIBUTES) { + def.validateAndSet(operation, model); + } + + KeycloakAdapterConfigService.INSTANCE.updateConfig(operation, model); + } +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceDefinition.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceDefinition.java new file mode 100644 index 0000000000..94dca67eee --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceDefinition.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016 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.server.extension; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.StringListAttributeDefinition; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; +import org.keycloak.subsystem.server.attributes.ModulesListAttributeBuilder; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ThemeResourceDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "theme"; + + // This is the internal name of the singleton resource + public static final String RESOURCE_NAME = "defaults"; + + // NOTE: All attributes must be SimpleAttributeDefinition. If that needs to + // change then refactor starting with lookup() method below. + static final SimpleAttributeDefinition STATIC_MAX_AGE = + new SimpleAttributeDefinitionBuilder("staticMaxAge", ModelType.LONG, true) + .setAllowExpression(true) + .setDefaultValue(new ModelNode("2592000")) + .setRestartAllServices() + .build(); + + static final SimpleAttributeDefinition CACHE_THEMES = + new SimpleAttributeDefinitionBuilder("cacheThemes", ModelType.BOOLEAN, true) + .setAllowExpression(true) + .setDefaultValue(new ModelNode(true)) + .setAllowNull(false) + .setRestartAllServices() + .build(); + + static final SimpleAttributeDefinition CACHE_TEMPLATES = + new SimpleAttributeDefinitionBuilder("cacheTemplates", ModelType.BOOLEAN, true) + .setAllowExpression(true) + .setDefaultValue(new ModelNode(true)) + .setAllowNull(false) + .setRestartAllServices() + .build(); + + static final SimpleAttributeDefinition WELCOME_THEME = + new SimpleAttributeDefinitionBuilder("welcomeTheme", ModelType.STRING, true) + .setAllowExpression(true) + .setRestartAllServices() + .build(); + + static final SimpleAttributeDefinition DEFAULT = + new SimpleAttributeDefinitionBuilder("default", ModelType.STRING, true) + .setAllowExpression(true) + .setRestartAllServices() + .build(); + + static final SimpleAttributeDefinition DIR = + new SimpleAttributeDefinitionBuilder("dir", ModelType.STRING, true) + .setAllowExpression(true) + .setDefaultValue(new ModelNode("${jboss.home.dir}/themes")) + .setRestartAllServices() + .build(); + + static final StringListAttributeDefinition MODULES = new ModulesListAttributeBuilder().build(); + + static final List ALL_ATTRIBUTES = new ArrayList<>(); + + static { + ALL_ATTRIBUTES.add(STATIC_MAX_AGE); + ALL_ATTRIBUTES.add(CACHE_THEMES); + ALL_ATTRIBUTES.add(CACHE_TEMPLATES); + ALL_ATTRIBUTES.add(WELCOME_THEME); + ALL_ATTRIBUTES.add(DEFAULT); + ALL_ATTRIBUTES.add(DIR); + ALL_ATTRIBUTES.add(MODULES); + } + + private static final Map DEFINITION_LOOKUP = new HashMap<>(); + static { + for (AttributeDefinition def : ALL_ATTRIBUTES) { + DEFINITION_LOOKUP.put(def.getXmlName(), def); + } + } + + protected static final ReloadRequiredWriteAttributeHandler WRITE_ATTR_HANDLER = new ReloadRequiredWriteAttributeHandler(ALL_ATTRIBUTES); + + protected ThemeResourceDefinition() { + super(PathElement.pathElement(TAG_NAME), + KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + ThemeResourceAddHandler.INSTANCE, + ThemeResourceRemoveHandler.INSTANCE + ); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + + for (AttributeDefinition def : ALL_ATTRIBUTES) { + resourceRegistration.registerReadWriteAttribute(def, null, WRITE_ATTR_HANDLER); + } + } + + public static SimpleAttributeDefinition lookup(String name) { + return (SimpleAttributeDefinition)DEFINITION_LOOKUP.get(name); + } + +} diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceRemoveHandler.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceRemoveHandler.java new file mode 100644 index 0000000000..797c564d8a --- /dev/null +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ThemeResourceRemoveHandler.java @@ -0,0 +1,32 @@ +/* + * Copyright 2016 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.server.extension; + +import org.jboss.as.controller.AbstractRemoveStepHandler; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class ThemeResourceRemoveHandler extends AbstractRemoveStepHandler { + + public static ThemeResourceRemoveHandler INSTANCE = new ThemeResourceRemoveHandler(); + + private ThemeResourceRemoveHandler() {} + +} diff --git a/wildfly/server-subsystem/src/main/resources/org/keycloak/subsystem/server/extension/LocalDescriptions.properties b/wildfly/server-subsystem/src/main/resources/org/keycloak/subsystem/server/extension/LocalDescriptions.properties index bf29d067e3..7417c13d3a 100755 --- a/wildfly/server-subsystem/src/main/resources/org/keycloak/subsystem/server/extension/LocalDescriptions.properties +++ b/wildfly/server-subsystem/src/main/resources/org/keycloak/subsystem/server/extension/LocalDescriptions.properties @@ -15,7 +15,38 @@ # limitations under the License. # +keycloak-server.migrate-json=Migrate keycloak-server.json to standalone.xml or domain.xml +keycloak-server.migrate-json.file=Optional local path to keycloak-server.json + keycloak-server.subsystem=Keycloak subsystem keycloak-server.subsystem.add=Operation Adds Keycloak subsystem keycloak-server.subsystem.remove=Operation removes Keycloak subsystem keycloak-server.subsystem.web-context=Web context where Keycloak server is bound. Default value is 'auth'. +keycloak-server.subsystem.providers=Paths to search for Keycloak provider jars. +keycloak-server.subsystem.master-realm-name=The name of the master admin realm. +keycloak-server.subsystem.scheduled-task-interval=The interval (in seconds) to run scheduled tasks. +keycloak-server.subsystem.spi=A Service Provider type. +keycloak-server.subsystem.theme=Theme configuration properties. + +keycloak-server.theme=Theme configuration properties. +keycloak-server.theme.add=Add the theme config properties. +keycloak-server.theme.remove=Remove the theme config properties. +keycloak-server.theme.staticMaxAge=Foo +keycloak-server.theme.cacheThemes=foo +keycloak-server.theme.cacheTemplates=foo +keycloak-server.theme.welcomeTheme=foo +keycloak-server.theme.default=foo +keycloak-server.theme.dir=Directory where themes can be located. +keycloak-server.theme.modules=List of modules containing themes. + +keycloak-server.spi=A Service Provider type. +keycloak-server.spi.add=Add an spi. +keycloak-server.spi.remove=Remove an spi. +keycloak-server.spi.default-provider=The default provider for the spi. +keycloak-server.spi.provider=A provider for the spi. + +keycloak-server.provider=A provider for the spi. +keycloak-server.provider.add=Add a provider. +keycloak-server.provider.remove=Remove a provider. +keycloak-server.provider.enabled=Enable or disable the provider. +keycloak-server.provider.properties=The properties for the provider. diff --git a/wildfly/server-subsystem/src/main/resources/schema/wildfly-keycloak-server_1_1.xsd b/wildfly/server-subsystem/src/main/resources/schema/wildfly-keycloak-server_1_1.xsd index df02124eb3..fa9f9e0478 100755 --- a/wildfly/server-subsystem/src/main/resources/schema/wildfly-keycloak-server_1_1.xsd +++ b/wildfly/server-subsystem/src/main/resources/schema/wildfly-keycloak-server_1_1.xsd @@ -1,21 +1,21 @@ +~ Copyright 2016 Red Hat, Inc. and/or its affiliates +~ and other contributors as indicated by the @author tags. +~ +~ 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. +--> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-server.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-server.xml index c269c314c3..f2d4cd6ae7 100644 --- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-server.xml +++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-server.xml @@ -1,25 +1,84 @@ +~ Copyright 2016 Red Hat, Inc. and/or its affiliates +~ and other contributors as indicated by the @author tags. +~ +~ 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. +--> - org.keycloak.keycloak-server-subsystem - - auth - + org.keycloak.keycloak-server-subsystem + + auth + + classpath:${jboss.home.dir}/providers/* + + master + 900 + + 2592000 + true + true + ${jboss.home.dir}/themes + + + jpa + + + + + + + + jpa + + + jpa + + + + + + jpa + + + jpa + + + basic + + + + + + + + + + + + + + + + + default + + + + + + + diff --git a/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/JsonConfigConverterTestCase.java b/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/JsonConfigConverterTestCase.java new file mode 100644 index 0000000000..4c6bf04f0d --- /dev/null +++ b/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/JsonConfigConverterTestCase.java @@ -0,0 +1,446 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.server.extension; + +import java.util.ArrayList; +import java.util.List; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.dmr.ModelNode; +import org.junit.Test; +import static org.junit.Assert.*; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class JsonConfigConverterTestCase { + + private final PathElement domainRoot = PathElement.pathElement("profile", "auth-server-clustered"); + private final PathAddress domainAddress = PathAddress.pathAddress(domainRoot) + .append(KeycloakExtension.PATH_SUBSYSTEM); + private final PathAddress standaloneAddress = PathAddress.pathAddress(KeycloakExtension.PATH_SUBSYSTEM); + + @Test + public void testConvertJsonStandaloneWithModules() throws Exception { + String json = basicJsonConfig(true); + List expResult = expectedOperations(true, false); + + List result = JsonConfigConverter.convertJsonConfig(json, standaloneAddress); + assertEquals(expResult, result); + } + + @Test + public void testConvertJsonStandaloneWithoutModules() throws Exception { + String json = basicJsonConfig(false); + List expResult = expectedOperations(false, false); + + List result = JsonConfigConverter.convertJsonConfig(json, standaloneAddress); + assertEquals(expResult, result); + } + + @Test + public void testConvertJsonDomainWithModules() throws Exception { + String json = basicJsonConfig(true); + List expResult = expectedOperations(true, true); + + List result = JsonConfigConverter.convertJsonConfig(json, domainAddress); + assertEquals(expResult, result); + } + + @Test + public void testConvertJsonDomainWithoutModules() throws Exception { + String json = basicJsonConfig(false); + List expResult = expectedOperations(false, true); + + List result = JsonConfigConverter.convertJsonConfig(json, domainAddress); + assertEquals(expResult, result); + } + + private String basicJsonConfig(boolean includeModules) { + String basicConfig = + "{\n" + + " \"providers\": [\n" + + " \"classpath:${jboss.home.dir}/providers/*\"\n" + + " ],\n" + + "\n" + + " \"admin\": {\n" + + " \"realm\": \"master\"\n" + + " },\n" + + "\n" + + " \"eventsStore\": {\n" + + " \"provider\": \"jpa\",\n" + + " \"jpa\": {\n" + + " \"exclude-events\": [ \"REFRESH_TOKEN\" ]\n" + + " }\n" + + " },\n" + + "\n" + + " \"realm\": {\n" + + " \"provider\": \"jpa\"\n" + + " },\n" + + "\n" + + " \"user\": {\n" + + " \"provider\": \"jpa\"\n" + + " },\n" + + "\n" + + " \"userCache\": {\n" + + " \"default\" : {\n" + + " \"enabled\": true\n" + + " }\n" + + " },\n" + + "\n" + + " \"userSessionPersister\": {\n" + + " \"provider\": \"jpa\"\n" + + " },\n" + + "\n" + + " \"authorizationPersister\": {\n" + + " \"provider\": \"jpa\"\n" + + " },\n" + + "\n" + + " \"timer\": {\n" + + " \"provider\": \"basic\"\n" + + " },\n" + + "\n" + + " \"theme\": {\n" + + " \"staticMaxAge\": 2592001,\n" + + " \"cacheTemplates\": false,\n" + + " \"cacheThemes\": false,\n" + + " \"welcomeTheme\": \"welcome\",\n" + + " \"default\": \"default\",\n" + + " \"folder\": {\n" + + " \"dir\": \"${jboss.home.dir}/themes\"\n"; + + + if (includeModules) { + basicConfig += + " },\n" + + " \"module\": {\n" + + " \"modules\": [ \"org.keycloak.example.themes\" ]\n" + + " }\n"; + } else { + basicConfig += + " }\n"; + } + + basicConfig += + " },\n" + + "\n" + + " \"scheduled\": {\n" + + " \"interval\": 900\n" + + " },\n" + + "\n" + + " \"connectionsHttpClient\": {\n" + + " \"default\": {}\n" + + " },\n" + + "\n" + + " \"connectionsJpa\": {\n" + + " \"default\": {\n" + + " \"dataSource\": \"java:jboss/datasources/KeycloakDS\",\n" + + " \"databaseSchema\": \"update\"\n" + + " }\n" + + " },\n" + + "\n" + + " \"realmCache\": {\n" + + " \"default\" : {\n" + + " \"enabled\": true\n" + + " }\n" + + " },\n" + + "\n" + + " \"connectionsInfinispan\": {\n" + + " \"provider\": \"default\",\n" + + " \"default\": {\n" + + " \"cacheContainer\" : \"java:comp/env/infinispan/Keycloak\"\n" + + " }\n" + + " }\n" + + "}"; + + return basicConfig; + } + + private List expectedOperations(boolean includeModules, boolean isDomain) { + List ops = new ArrayList<>(); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"write-attribute\",\n" + + " \"address\" => [(\"subsystem\" => \"keycloak-server\")],\n" + + " \"name\" => \"master-realm-name\",\n" + + " \"value\" => \"master\"\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"write-attribute\",\n" + + " \"address\" => [(\"subsystem\" => \"keycloak-server\")],\n" + + " \"name\" => \"scheduled-task-interval\",\n" + + " \"value\" => 900L\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"write-attribute\",\n" + + " \"address\" => [(\"subsystem\" => \"keycloak-server\")],\n" + + " \"name\" => \"providers\",\n" + + " \"value\" => [\"classpath:${jboss.home.dir}/providers/*\"]\n" + + "}" + )); + + if (includeModules) { + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"theme\" => \"defaults\")\n" + + " ],\n" + + " \"staticMaxAge\" => 2592001L,\n" + + " \"cacheTemplates\" => false,\n" + + " \"cacheThemes\" => false,\n" + + " \"dir\" => \"${jboss.home.dir}/themes\",\n" + + " \"welcomeTheme\" => \"welcome\",\n" + + " \"default\" => \"default\",\n" + + " \"modules\" => [\"org.keycloak.example.themes\"]\n" + + "}" + )); + } else { + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"theme\" => \"defaults\")\n" + + " ],\n" + + " \"staticMaxAge\" => 2592001L,\n" + + " \"cacheTemplates\" => false,\n" + + " \"cacheThemes\" => false,\n" + + " \"dir\" => \"${jboss.home.dir}/themes\",\n" + + " \"welcomeTheme\" => \"welcome\",\n" + + " \"default\" => \"default\",\n" + + "}" + )); + } + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"eventsStore\")\n" + + " ],\n" + + " \"default-provider\" => \"jpa\"\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"eventsStore\"),\n" + + " (\"provider\" => \"jpa\")\n" + + " ],\n" + + " \"properties\" => {\"exclude-events\" => \"[\\\"REFRESH_TOKEN\\\"]\"},\n" + + " \"enabled\" => true\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"realm\")\n" + + " ],\n" + + " \"default-provider\" => \"jpa\"\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"user\")\n" + + " ],\n" + + " \"default-provider\" => \"jpa\"\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"userCache\")\n" + + " ]\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"userCache\"),\n" + + " (\"provider\" => \"default\")\n" + + " ],\n" + + " \"enabled\" => true\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"userSessionPersister\")\n" + + " ],\n" + + " \"default-provider\" => \"jpa\"\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"authorizationPersister\")\n" + + " ],\n" + + " \"default-provider\" => \"jpa\"\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"timer\")\n" + + " ],\n" + + " \"default-provider\" => \"basic\"\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"connectionsHttpClient\")\n" + + " ]\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"connectionsHttpClient\"),\n" + + " (\"provider\" => \"default\")\n" + + " ],\n" + + " \"enabled\" => true\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"connectionsJpa\")\n" + + " ]\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"connectionsJpa\"),\n" + + " (\"provider\" => \"default\")\n" + + " ],\n" + + " \"properties\" => {\n" + + " \"dataSource\" => \"java:jboss/datasources/KeycloakDS\",\n" + + " \"databaseSchema\" => \"update\"\n" + + " },\n" + + " \"enabled\" => true\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"realmCache\")\n" + + " ]\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"realmCache\"),\n" + + " (\"provider\" => \"default\")\n" + + " ],\n" + + " \"enabled\" => true\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"connectionsInfinispan\")\n" + + " ],\n" + + " \"default-provider\" => \"default\"\n" + + "}" + )); + + ops.add(ModelNode.fromString( + "{\n" + + " \"operation\" => \"add\",\n" + + " \"address\" => [\n" + + " (\"subsystem\" => \"keycloak-server\"),\n" + + " (\"spi\" => \"connectionsInfinispan\"),\n" + + " (\"provider\" => \"default\")\n" + + " ],\n" + + " \"properties\" => {\"cacheContainer\" => \"java:comp/env/infinispan/Keycloak\"},\n" + + " \"enabled\" => true\n" + + "}" + )); + + if (isDomain) { // prepend the domain root + for (ModelNode op : ops) { + PathAddress addr = PathAddress.pathAddress(op.get(ADDRESS)); + PathAddress domainAddr = PathAddress.pathAddress(domainRoot).append(addr); + op.get(ADDRESS).set(domainAddr.toModelNode()); + } + } + + return ops; + } +} \ No newline at end of file diff --git a/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/SubsystemParsingTestCase.java b/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/SubsystemParsingTestCase.java index c84acc26e2..ade4a0574c 100755 --- a/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/SubsystemParsingTestCase.java +++ b/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/SubsystemParsingTestCase.java @@ -17,10 +17,9 @@ package org.keycloak.subsystem.server.extension; import java.io.IOException; +import java.util.Properties; import org.jboss.as.subsystem.test.AbstractSubsystemBaseTest; -import org.jboss.dmr.ModelNode; -import org.junit.Test; /** * Tests all management expects for subsystem, parsing, marshaling, model definition and other @@ -38,14 +37,13 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest { super(KeycloakExtension.SUBSYSTEM_NAME, new KeycloakExtension()); } - @Test - public void testJson() throws Exception { - ModelNode node = new ModelNode(); - node.get("web-context").set("auth"); - - System.out.println("json=" + node.toJSONString(false)); + @Override + protected Properties getResolvedProperties() { + Properties properties = new Properties(); + properties.put("jboss.home.dir", System.getProperty("java.io.tmpdir")); + return properties; } - + @Override protected String getSubsystemXml() throws IOException { return readResource("keycloak-server-1.1.xml"); diff --git a/wildfly/server-subsystem/src/test/resources/org/keycloak/subsystem/server/extension/keycloak-server-1.1.xml b/wildfly/server-subsystem/src/test/resources/org/keycloak/subsystem/server/extension/keycloak-server-1.1.xml index 103e1625fc..290d8b7760 100644 --- a/wildfly/server-subsystem/src/test/resources/org/keycloak/subsystem/server/extension/keycloak-server-1.1.xml +++ b/wildfly/server-subsystem/src/test/resources/org/keycloak/subsystem/server/extension/keycloak-server-1.1.xml @@ -1,20 +1,79 @@ +~ Copyright 2016 Red Hat, Inc. and/or its affiliates +~ and other contributors as indicated by the @author tags. +~ +~ 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. +--> auth + + classpath:${jboss.home.dir}/providers/* + + master + 900 + + 2592000 + true + true + ${jboss.home.dir}/themes + + + jpa + + + + + + + + jpa + + + jpa + + + + + + jpa + + + jpa + + + basic + + + + + + + + + + + + + + + + + default + + + + + + \ No newline at end of file From ef442cae926c428bf706abe49666ae5fe654b4c2 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Wed, 17 Aug 2016 15:18:13 -0400 Subject: [PATCH 17/49] KEYCLOAK-3196: Create single place to declare default keycloak subsystem config. --- distribution/demo-dist/pom.xml | 25 +++++ .../demo-dist/src/main/xslt/standalone.xsl | 95 ++++--------------- .../cli/keycloak-install-ha.cli | 4 +- .../server-overlay/cli/keycloak-install.cli | 5 +- wildfly/server-subsystem/pom.xml | 17 +++- .../default-server-subsys-config.properties | 67 +++++++++++++ .../server/extension/JsonConfigConverter.java | 2 +- .../keycloak-server-default-config.xml | 1 + .../subsystem-templates/keycloak-server.xml | 63 +----------- .../extension/SubsystemParsingTestCase.java | 1 + .../server/extension/keycloak-server-1.1.xml | 80 +--------------- 11 files changed, 137 insertions(+), 223 deletions(-) create mode 100644 wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties create mode 100644 wildfly/server-subsystem/src/main/resources/default-config/keycloak-server-default-config.xml diff --git a/distribution/demo-dist/pom.xml b/distribution/demo-dist/pom.xml index 187d62dcec..71a5a1a1ed 100755 --- a/distribution/demo-dist/pom.xml +++ b/distribution/demo-dist/pom.xml @@ -202,6 +202,31 @@ + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack + compile + + unpack + + + + + org.keycloak + keycloak-wildfly-server-subsystem + ${project.version} + jar + default-config/*.xml + + + + + + diff --git a/distribution/demo-dist/src/main/xslt/standalone.xsl b/distribution/demo-dist/src/main/xslt/standalone.xsl index db2247c43e..855efc0c56 100755 --- a/distribution/demo-dist/src/main/xslt/standalone.xsl +++ b/distribution/demo-dist/src/main/xslt/standalone.xsl @@ -1,19 +1,19 @@ +~ Copyright 2016 Red Hat, Inc. and/or its affiliates +~ and other contributors as indicated by the @author tags. +~ +~ 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. +--> - + jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE h2 @@ -57,68 +57,7 @@ - - auth - - classpath:${jboss.home.dir}/providers/* - - master - 900 - - 2592000 - true - true - ${jboss.home.dir}/themes - - - jpa - - - - - - - - jpa - - - jpa - - - - - - jpa - - - jpa - - - basic - - - - - - - - - - - - - - - - - default - - - - - - - + diff --git a/distribution/server-overlay/cli/keycloak-install-ha.cli b/distribution/server-overlay/cli/keycloak-install-ha.cli index f52bbc050a..ddcefc460b 100644 --- a/distribution/server-overlay/cli/keycloak-install-ha.cli +++ b/distribution/server-overlay/cli/keycloak-install-ha.cli @@ -1,5 +1,5 @@ embed-server --server-config=standalone-ha.xml -/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true) +/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",jta=false,driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true) /subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak") /subsystem=infinispan/cache-container=keycloak/transport=TRANSPORT:add(lock-timeout=60000) /subsystem=infinispan/cache-container=keycloak/invalidation-cache=realms:add(mode="SYNC") @@ -30,3 +30,5 @@ embed-server --server-config=standalone-ha.xml /subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true) /subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default) /subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true) +/subsystem=keycloak-server/spi=jta-lookup/:add +/subsystem=keycloak-server/spi=jta-lookup/provider=jboss/:add(enabled=true) diff --git a/distribution/server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/cli/keycloak-install.cli index c3ca492b8a..c89859d38c 100644 --- a/distribution/server-overlay/cli/keycloak-install.cli +++ b/distribution/server-overlay/cli/keycloak-install.cli @@ -1,5 +1,5 @@ embed-server --server-config=standalone.xml -/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true) +/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",jta=false,driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true) /subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak") /subsystem=infinispan/cache-container=keycloak/local-cache=realms:add() /subsystem=infinispan/cache-container=keycloak/local-cache=users:add() @@ -30,3 +30,6 @@ embed-server --server-config=standalone.xml /subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true) /subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default) /subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true) +/subsystem=keycloak-server/spi=jta-lookup/:add +/subsystem=keycloak-server/spi=jta-lookup/provider=jboss/:add(enabled=true) + diff --git a/wildfly/server-subsystem/pom.xml b/wildfly/server-subsystem/pom.xml index db0f3cac78..05b0fc0c0b 100755 --- a/wildfly/server-subsystem/pom.xml +++ b/wildfly/server-subsystem/pom.xml @@ -47,8 +47,23 @@ **/*TestCase.java - + + + src/main/config/default-server-subsys-config.properties + + + + src/main/resources + true + + + + + src/test/resources + true + + diff --git a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties new file mode 100644 index 0000000000..afdebe22a9 --- /dev/null +++ b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties @@ -0,0 +1,67 @@ +keycloak.server.subsys.default.config=\ +\ + auth\ + \ + classpath:${jboss.home.dir}/providers/*\ + \ + master\ + 900\ + \ + 2592000\ + true\ + true\ + ${jboss.home.dir}/themes\ + \ + \ + jpa\ + \ + \ + \ + \ + \ + \ + \ + jpa\ + \ + \ + jpa\ + \ + \ + \ + \ + \ + jpa\ + \ + \ + jpa\ + \ + \ + basic\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + default\ + \ + \ + \ + \ + \ + \ + \ + ${keycloak.jta.lookup.provider:jboss}\ + \ + \ +\ \ No newline at end of file diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/JsonConfigConverter.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/JsonConfigConverter.java index 95010cc073..aa4d3f578d 100644 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/JsonConfigConverter.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/JsonConfigConverter.java @@ -47,7 +47,7 @@ import static org.keycloak.subsystem.server.extension.ThemeResourceDefinition.WE */ public class JsonConfigConverter { - private static final List NON_SPI_LIST = new ArrayList(); + private static final List NON_SPI_LIST = new ArrayList<>(); static { NON_SPI_LIST.add("providers"); diff --git a/wildfly/server-subsystem/src/main/resources/default-config/keycloak-server-default-config.xml b/wildfly/server-subsystem/src/main/resources/default-config/keycloak-server-default-config.xml new file mode 100644 index 0000000000..e5206116c6 --- /dev/null +++ b/wildfly/server-subsystem/src/main/resources/default-config/keycloak-server-default-config.xml @@ -0,0 +1 @@ +${keycloak.server.subsys.default.config} \ No newline at end of file diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-server.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-server.xml index f2d4cd6ae7..67bf03e733 100644 --- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-server.xml +++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-server.xml @@ -19,66 +19,5 @@ org.keycloak.keycloak-server-subsystem - - auth - - classpath:${jboss.home.dir}/providers/* - - master - 900 - - 2592000 - true - true - ${jboss.home.dir}/themes - - - jpa - - - - - - - - jpa - - - jpa - - - - - - jpa - - - jpa - - - basic - - - - - - - - - - - - - - - - - default - - - - - - - + ${keycloak.server.subsys.default.config} diff --git a/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/SubsystemParsingTestCase.java b/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/SubsystemParsingTestCase.java index ade4a0574c..0bc41106dd 100755 --- a/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/SubsystemParsingTestCase.java +++ b/wildfly/server-subsystem/src/test/java/org/keycloak/subsystem/server/extension/SubsystemParsingTestCase.java @@ -41,6 +41,7 @@ public class SubsystemParsingTestCase extends AbstractSubsystemBaseTest { protected Properties getResolvedProperties() { Properties properties = new Properties(); properties.put("jboss.home.dir", System.getProperty("java.io.tmpdir")); + properties.put("keycloak.jta.lookup.provider", "jboss"); return properties; } diff --git a/wildfly/server-subsystem/src/test/resources/org/keycloak/subsystem/server/extension/keycloak-server-1.1.xml b/wildfly/server-subsystem/src/test/resources/org/keycloak/subsystem/server/extension/keycloak-server-1.1.xml index 290d8b7760..e5206116c6 100644 --- a/wildfly/server-subsystem/src/test/resources/org/keycloak/subsystem/server/extension/keycloak-server-1.1.xml +++ b/wildfly/server-subsystem/src/test/resources/org/keycloak/subsystem/server/extension/keycloak-server-1.1.xml @@ -1,79 +1 @@ - - - - auth - - classpath:${jboss.home.dir}/providers/* - - master - 900 - - 2592000 - true - true - ${jboss.home.dir}/themes - - - jpa - - - - - - - - jpa - - - jpa - - - - - - jpa - - - jpa - - - basic - - - - - - - - - - - - - - - - - default - - - - - - - \ No newline at end of file +${keycloak.server.subsys.default.config} \ No newline at end of file From 1489bd734ae0d3c0841c080f0bf2f9bfffb3a7c7 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Thu, 18 Aug 2016 11:39:50 -0400 Subject: [PATCH 18/49] KEYCLOAK-3196: Add CLI help text. --- .../server/extension/LocalDescriptions.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wildfly/server-subsystem/src/main/resources/org/keycloak/subsystem/server/extension/LocalDescriptions.properties b/wildfly/server-subsystem/src/main/resources/org/keycloak/subsystem/server/extension/LocalDescriptions.properties index 7417c13d3a..8d5c5a780d 100755 --- a/wildfly/server-subsystem/src/main/resources/org/keycloak/subsystem/server/extension/LocalDescriptions.properties +++ b/wildfly/server-subsystem/src/main/resources/org/keycloak/subsystem/server/extension/LocalDescriptions.properties @@ -31,11 +31,11 @@ keycloak-server.subsystem.theme=Theme configuration properties. keycloak-server.theme=Theme configuration properties. keycloak-server.theme.add=Add the theme config properties. keycloak-server.theme.remove=Remove the theme config properties. -keycloak-server.theme.staticMaxAge=Foo -keycloak-server.theme.cacheThemes=foo -keycloak-server.theme.cacheTemplates=foo -keycloak-server.theme.welcomeTheme=foo -keycloak-server.theme.default=foo +keycloak-server.theme.staticMaxAge=Maximum time the browser should cache theme resources. A value of -1 will disable caching. +keycloak-server.theme.cacheThemes=If true, themes are cached. +keycloak-server.theme.cacheTemplates=If true, theme templates are cached. +keycloak-server.theme.welcomeTheme=The welcome theme. +keycloak-server.theme.default=The default theme to use if no theme is specified for a realm. keycloak-server.theme.dir=Directory where themes can be located. keycloak-server.theme.modules=List of modules containing themes. From e4d97485ecdc90ca7aa733b5a853ec2388ae2488 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Mon, 22 Aug 2016 16:09:16 -0400 Subject: [PATCH 19/49] KEYCLOAK-3196: Create master cli script for server-subsystem. --- distribution/server-overlay/assembly.xml | 5 ++++ .../cli/keycloak-install-ha.cli | 22 +----------------- .../server-overlay/cli/keycloak-install.cli | 23 +------------------ distribution/server-overlay/pom.xml | 19 +++++++++++++++ .../resources/KeycloakApplication.java | 9 -------- .../default-server-subsys-config.properties | 4 ++++ .../cli/default-keycloak-subsys-config.cli | 21 +++++++++++++++++ 7 files changed, 51 insertions(+), 52 deletions(-) create mode 100644 wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli diff --git a/distribution/server-overlay/assembly.xml b/distribution/server-overlay/assembly.xml index 9240a1000b..162bd00764 100755 --- a/distribution/server-overlay/assembly.xml +++ b/distribution/server-overlay/assembly.xml @@ -94,6 +94,11 @@ bin add-user-keycloak.bat + + ${project.build.directory}/cli/default-keycloak-subsys-config.cli + bin + default-keycloak-subsys-config.cli + diff --git a/distribution/server-overlay/cli/keycloak-install-ha.cli b/distribution/server-overlay/cli/keycloak-install-ha.cli index ddcefc460b..bc7d8639d2 100644 --- a/distribution/server-overlay/cli/keycloak-install-ha.cli +++ b/distribution/server-overlay/cli/keycloak-install-ha.cli @@ -11,24 +11,4 @@ embed-server --server-config=standalone-ha.xml /subsystem=infinispan/cache-container=keycloak/distributed-cache=authorization:add(mode="SYNC",owners="1") /subsystem=infinispan/cache-container=keycloak/replicated-cache=work:add(mode="SYNC") /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) -/subsystem=keycloak-server:add(web-context=auth,master-realm-name=master,scheduled-task-interval=900,providers=[classpath:${jboss.home.dir}/providers/*]) -/subsystem=keycloak-server/theme=defaults/:add(dir=${jboss.home.dir}/themes,staticMaxAge=2592000,cacheTemplates=true,cacheThemes=true) -/subsystem=keycloak-server/spi=eventsStore/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true) -/subsystem=keycloak-server/spi=realm/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=user/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=userCache/:add -/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=true) -/subsystem=keycloak-server/spi=userSessionPersister/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=authorizationPersister/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=timer/:add(default-provider=basic) -/subsystem=keycloak-server/spi=connectionsHttpClient/:add -/subsystem=keycloak-server/spi=connectionsHttpClient/provider=default/:add(enabled=true) -/subsystem=keycloak-server/spi=connectionsJpa/:add -/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",databaseSchema => "update"},enabled=true) -/subsystem=keycloak-server/spi=realmCache/:add -/subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true) -/subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default) -/subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true) -/subsystem=keycloak-server/spi=jta-lookup/:add -/subsystem=keycloak-server/spi=jta-lookup/provider=jboss/:add(enabled=true) +run-batch --file=default-keycloak-subsys-config.cli diff --git a/distribution/server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/cli/keycloak-install.cli index c89859d38c..dbb2c32eac 100644 --- a/distribution/server-overlay/cli/keycloak-install.cli +++ b/distribution/server-overlay/cli/keycloak-install.cli @@ -11,25 +11,4 @@ embed-server --server-config=standalone.xml /subsystem=infinispan/cache-container=keycloak/local-cache=authorization:add() /subsystem=infinispan/cache-container=keycloak/local-cache=authorization/eviction=EVICTION:add(max-entries=100,strategy=LRU) /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) -/subsystem=keycloak-server:add(web-context=auth,master-realm-name=master,scheduled-task-interval=900,providers=[classpath:${jboss.home.dir}/providers/*]) -/subsystem=keycloak-server/theme=defaults/:add(dir=${jboss.home.dir}/themes,staticMaxAge=2592000,cacheTemplates=true,cacheThemes=true) -/subsystem=keycloak-server/spi=eventsStore/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true) -/subsystem=keycloak-server/spi=realm/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=user/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=userCache/:add -/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=true) -/subsystem=keycloak-server/spi=userSessionPersister/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=authorizationPersister/:add(default-provider=jpa) -/subsystem=keycloak-server/spi=timer/:add(default-provider=basic) -/subsystem=keycloak-server/spi=connectionsHttpClient/:add -/subsystem=keycloak-server/spi=connectionsHttpClient/provider=default/:add(enabled=true) -/subsystem=keycloak-server/spi=connectionsJpa/:add -/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",databaseSchema => "update"},enabled=true) -/subsystem=keycloak-server/spi=realmCache/:add -/subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true) -/subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default) -/subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true) -/subsystem=keycloak-server/spi=jta-lookup/:add -/subsystem=keycloak-server/spi=jta-lookup/provider=jboss/:add(enabled=true) - +run-batch --file=default-keycloak-subsys-config.cli \ No newline at end of file diff --git a/distribution/server-overlay/pom.xml b/distribution/server-overlay/pom.xml index 96400d14e9..900bc29657 100755 --- a/distribution/server-overlay/pom.xml +++ b/distribution/server-overlay/pom.xml @@ -45,6 +45,25 @@ org.apache.maven.plugins maven-dependency-plugin + + unpack + compile + + unpack + + + + + org.keycloak + keycloak-wildfly-server-subsystem + ${project.version} + jar + cli/*.cli + ${project.build.directory} + + + + unpack-server-dist prepare-package diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index b76ff546b5..e8eca80bd4 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -233,18 +233,9 @@ public class KeycloakApplication extends Application { } } - if (node == null) { - URL resource = Thread.currentThread().getContextClassLoader().getResource("META-INF/keycloak-server.json"); - if (resource != null) { - logger.loadingFrom(resource); - node = new ObjectMapper().readTree(resource); - } - } - if (node != null) { Properties properties = new SystemEnvProperties(); Config.init(new JsonConfigProvider(node, properties)); - return; } else { throw new RuntimeException("Keycloak config not found."); } diff --git a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties index afdebe22a9..61084f93b9 100644 --- a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties +++ b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties @@ -1,3 +1,7 @@ +# IMPORTANT: If you change this file you should also make equivalent changes +# to src/main/resources/cli/default-keycloak-subsys-config.cli +# The CLI file is packaged with the subsystem and extracted by the overlay distribution. + keycloak.server.subsys.default.config=\ \ auth\ diff --git a/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli b/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli new file mode 100644 index 0000000000..8bfefa14ad --- /dev/null +++ b/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli @@ -0,0 +1,21 @@ +/subsystem=keycloak-server:add(web-context=auth,master-realm-name=master,scheduled-task-interval=900,providers=[classpath:${jboss.home.dir}/providers/*]) +/subsystem=keycloak-server/theme=defaults/:add(dir=${jboss.home.dir}/themes,staticMaxAge=2592000,cacheTemplates=true,cacheThemes=true) +/subsystem=keycloak-server/spi=eventsStore/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=eventsStore/provider=jpa/:add(properties={exclude-events => "[\"REFRESH_TOKEN\"]"},enabled=true) +/subsystem=keycloak-server/spi=realm/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=user/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=userCache/:add +/subsystem=keycloak-server/spi=userCache/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=userSessionPersister/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=authorizationPersister/:add(default-provider=jpa) +/subsystem=keycloak-server/spi=timer/:add(default-provider=basic) +/subsystem=keycloak-server/spi=connectionsHttpClient/:add +/subsystem=keycloak-server/spi=connectionsHttpClient/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=connectionsJpa/:add +/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",databaseSchema => "update"},enabled=true) +/subsystem=keycloak-server/spi=realmCache/:add +/subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true) +/subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default) +/subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true) +/subsystem=keycloak-server/spi=jta-lookup/:add +/subsystem=keycloak-server/spi=jta-lookup/provider=jboss/:add(enabled=true) \ No newline at end of file From 3abcf713e5a4331b9a8d6e07892c3c55adec0bb2 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Tue, 23 Aug 2016 11:14:40 -0400 Subject: [PATCH 20/49] KEYCLOAK-3196: Test need ability to load keycloak-server.json from /META-INF --- .../keycloak/services/resources/KeycloakApplication.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index e8eca80bd4..66a643ce13 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -233,6 +233,14 @@ public class KeycloakApplication extends Application { } } + if (node == null) { + URL resource = Thread.currentThread().getContextClassLoader().getResource("META-INF/keycloak-server.json"); + if (resource != null) { + logger.loadingFrom(resource); + node = new ObjectMapper().readTree(resource); + } + } + if (node != null) { Properties properties = new SystemEnvProperties(); Config.init(new JsonConfigProvider(node, properties)); From 09090cec2465f16624954a29916743e9306b0d3e Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Tue, 23 Aug 2016 11:56:21 -0400 Subject: [PATCH 21/49] KEYCLOAK-3196: Resolve conflicts. Port keycloak-server.json changes to xml and cli --- .../src/main/config/default-server-subsys-config.properties | 4 +++- .../src/main/resources/cli/default-keycloak-subsys-config.cli | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties index 61084f93b9..2355ab0e7e 100644 --- a/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties +++ b/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties @@ -49,7 +49,9 @@ keycloak.server.subsys.default.config=\ \ \ \ - \ + \ + \ + \ \ \ \ diff --git a/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli b/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli index 8bfefa14ad..6adef01e49 100644 --- a/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli +++ b/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli @@ -12,10 +12,10 @@ /subsystem=keycloak-server/spi=connectionsHttpClient/:add /subsystem=keycloak-server/spi=connectionsHttpClient/provider=default/:add(enabled=true) /subsystem=keycloak-server/spi=connectionsJpa/:add -/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",databaseSchema => "update"},enabled=true) +/subsystem=keycloak-server/spi=connectionsJpa/provider=default/:add(properties={dataSource => "java:jboss/datasources/KeycloakDS",initializeEmpty => "true",migrationStrategy => "update",migrationExport => "${jboss.home.dir}/keycloak-database-update.sql"},enabled=true) /subsystem=keycloak-server/spi=realmCache/:add /subsystem=keycloak-server/spi=realmCache/provider=default/:add(enabled=true) /subsystem=keycloak-server/spi=connectionsInfinispan/:add(default-provider=default) /subsystem=keycloak-server/spi=connectionsInfinispan/provider=default/:add(properties={cacheContainer => "java:comp/env/infinispan/Keycloak"},enabled=true) -/subsystem=keycloak-server/spi=jta-lookup/:add +/subsystem=keycloak-server/spi=jta-lookup/:add(default-provider=${keycloak.jta.lookup.provider:jboss}) /subsystem=keycloak-server/spi=jta-lookup/provider=jboss/:add(enabled=true) \ No newline at end of file From 345163994a05b1783daa1e19a6103d45e0ff26a0 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Tue, 23 Aug 2016 13:02:11 -0400 Subject: [PATCH 22/49] KEYCLOAK-3196: Allow expressions in SPI properties. --- .../subsystem/server/extension/ProviderResourceDefinition.java | 1 + 1 file changed, 1 insertion(+) diff --git a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceDefinition.java b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceDefinition.java index 0a4bf36845..68ba9bb327 100644 --- a/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceDefinition.java +++ b/wildfly/server-subsystem/src/main/java/org/keycloak/subsystem/server/extension/ProviderResourceDefinition.java @@ -46,6 +46,7 @@ public class ProviderResourceDefinition extends SimpleResourceDefinition { static final PropertiesAttributeDefinition PROPERTIES = new PropertiesAttributeDefinition.Builder("properties", true) .setRestartAllServices() + .setAllowExpression(true) .build(); protected static final ReloadRequiredWriteAttributeHandler WRITE_ATTR_HANDLER = new ReloadRequiredWriteAttributeHandler(ENABLED); From 99f453b25cf06b54c16b0ffe462f8bb3601081a0 Mon Sep 17 00:00:00 2001 From: Stephen Russett Date: Wed, 24 Aug 2016 23:40:21 -0400 Subject: [PATCH 23/49] typo fix --- examples/broker/twitter-authentication/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/broker/twitter-authentication/README.md b/examples/broker/twitter-authentication/README.md index 5e93083bbf..8f33d2b3e2 100644 --- a/examples/broker/twitter-authentication/README.md +++ b/examples/broker/twitter-authentication/README.md @@ -3,7 +3,7 @@ What is it? ----------- -This example demonstrates how to use Social Ientity Providers with Keycloak to authenticate users. In this case, +This example demonstrates how to use Social Identity Providers with Keycloak to authenticate users. In this case, users are authenticated with Twitter using Keycloak Identity Broker capabilities using the oAuth 2 protocol. From this example, you'll learn how to: @@ -179,4 +179,4 @@ Debug the Application If you want to debug the source code or look at the Javadocs of any library in the project, run either of the following commands to pull them into your local repository. The IDE should then detect them. mvn dependency:sources - mvn dependency:resolve -Dclassifier=javadoc \ No newline at end of file + mvn dependency:resolve -Dclassifier=javadoc From cfd61dc359cd713ac01fc2d76e4612df33e43b36 Mon Sep 17 00:00:00 2001 From: Stephen Russett Date: Wed, 24 Aug 2016 23:45:52 -0400 Subject: [PATCH 24/49] typo fix in google example readme --- examples/broker/google-authentication/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/broker/google-authentication/README.md b/examples/broker/google-authentication/README.md index 0825aed632..48ddca70ae 100644 --- a/examples/broker/google-authentication/README.md +++ b/examples/broker/google-authentication/README.md @@ -3,7 +3,7 @@ What is it? ----------- -This example demonstrates how to use Social Ientity Providers with Keycloak to authenticate users. In this case, +This example demonstrates how to use Social Identity Providers with Keycloak to authenticate users. In this case, users are authenticated with Google using Keycloak Identity Broker capabilities using the oAuth 2 protocol. From this example, you'll learn how to: @@ -180,4 +180,4 @@ Debug the Application If you want to debug the source code or look at the Javadocs of any library in the project, run either of the following commands to pull them into your local repository. The IDE should then detect them. mvn dependency:sources - mvn dependency:resolve -Dclassifier=javadoc \ No newline at end of file + mvn dependency:resolve -Dclassifier=javadoc From 099de9e6e3f081bc62e345302368d9563a42e5c5 Mon Sep 17 00:00:00 2001 From: Vlasta Ramik Date: Thu, 25 Aug 2016 12:20:18 +0200 Subject: [PATCH 25/49] KEYCLOAK-3459 Adapt testsuite according to server configuration inside standalone.xml instead of keycloak-server.json --- .../auth-server/jboss/build-truststore.xml | 52 ----------- .../servers/auth-server/jboss/build.xml | 42 --------- .../common/keycloak-server-subsystem.xsl | 65 ++++++++++++++ .../servers/auth-server/jboss/pom.xml | 88 ++++--------------- 4 files changed, 84 insertions(+), 163 deletions(-) delete mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/build-truststore.xml delete mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/build.xml create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/build-truststore.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/build-truststore.xml deleted file mode 100644 index 073f860a32..0000000000 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/build-truststore.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml deleted file mode 100644 index 1e4ab786e5..0000000000 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/build.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl new file mode 100644 index 0000000000..f32c036600 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/keycloak-server-subsystem.xsl @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + module:org.keycloak.testsuite.integration-arquillian-testsuite-providers + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml index 4fab304829..eae0512470 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml @@ -142,74 +142,6 @@ - - org.apache.maven.plugins - maven-antrun-plugin - 1.8 - - - inject-into-keycloak-server-json - process-resources - - run - - - - - - - - - - - inject-truststore-into-keycloak-server-json - process-resources - - run - - - - - - - - - - - - - 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 - - - org.keycloak - keycloak-core - ${project.version} - - - maven-enforcer-plugin @@ -247,6 +179,25 @@ + + inject-provider-and-truststore + process-resources + + transform + + + + + ${auth.server.home}/standalone/configuration + + standalone.xml + + ${common.resources}/keycloak-server-subsystem.xsl + ${auth.server.home}/standalone/configuration + + + + @@ -318,7 +269,6 @@ - server-overlay From 53557d9cfd09cc7ba0b594d835ab4274277157ff Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Fri, 26 Aug 2016 10:46:45 +0900 Subject: [PATCH 26/49] KEYCLOAK-3397 Fix Japanese translation --- .../theme/base/account/messages/messages_ja.properties | 4 ++-- .../base/admin/messages/admin-messages_ja.properties | 10 +++++----- .../theme/base/admin/messages/messages_ja.properties | 4 ++-- .../theme/base/login/messages/messages_ja.properties | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/themes/src/main/resources/theme/base/account/messages/messages_ja.properties b/themes/src/main/resources/theme/base/account/messages/messages_ja.properties index 2aff0887e1..82c9cb537b 100644 --- a/themes/src/main/resources/theme/base/account/messages/messages_ja.properties +++ b/themes/src/main/resources/theme/base/account/messages/messages_ja.properties @@ -148,6 +148,6 @@ invalidPasswordMinLowerCaseCharsMessage=無効なパスワード: 少なくと invalidPasswordMinDigitsMessage=無効なパスワード: 少なくとも {0} 文字の数字を含む必要があります。 invalidPasswordMinUpperCaseCharsMessage=無効なパスワード: 少なくとも {0} 文字の大文字を含む必要があります。 invalidPasswordMinSpecialCharsMessage=無効なパスワード: 少なくとも {0} 文字の特殊文字を含む必要があります。 -invalidPasswordNotUsernameMessage=無効なパスワード: ユーザー名と同じは禁止されています。 +invalidPasswordNotUsernameMessage=無効なパスワード: ユーザー名と同じパスワードは禁止されています。 invalidPasswordRegexPatternMessage=無効なパスワード: 正規表現パターンと一致しません。 -invalidPasswordHistoryMessage=無効なパスワード: 最近の {0} パスワードのいずれかと同じは禁止されています。 \ No newline at end of file +invalidPasswordHistoryMessage=無効なパスワード: 最近の {0} パスワードのいずれかと同じパスワードは禁止されています。 \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ja.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ja.properties index 975a7436b7..f99e168ed9 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_ja.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_ja.properties @@ -106,7 +106,7 @@ content-type-options-tooltip=デフォルト値では Internet Explorer と Goog max-login-failures=最大ログイン失敗回数 max-login-failures.tooltip=検出するまでの失敗回数です。 wait-increment=連続失敗時の待機時間 -wait-increment.tooltip=失敗回数が閾値を満たした場合、どれくらいの時間ユーザーはロックアウトされるか設定します。 +wait-increment.tooltip=失敗回数が閾値に達した場合、どれくらいの時間ユーザーはロックアウトされるか設定します。 quick-login-check-millis=クイックログイン試行間のミリ秒数チェック quick-login-check-millis.tooltip=クイックログイン失敗があまりにも頻繁に発生した場合は、ユーザーをロックアウトします。 min-quick-login-wait=クイックログイン失敗時の最小待機時間 @@ -193,13 +193,13 @@ client-protocol.tooltip=「OpenID Connect」 は、認可サーバーによっ access-type=アクセスタイプ access-type.tooltip=「Confidential」 クライアントはログインプロトコルの開始するためにシークレットを必要とします。 「Public」 クライアントはシークレットを必要としません。 「Bearer-only」 クライアントはログインを開始することはない Web サービスです。 standard-flow-enabled=Standard Flow の有効 -standard-flow-enabled.tooltip=OpenID Connect の標準的な、認可コードによるリダイレクトベースの認証を有効にします。 OpenID Connect または OAuth2 の仕様でいうと、 「Authorization Code Flow」 のサポートを有効にします。 +standard-flow-enabled.tooltip=OpenID Connect の標準的な、認可コードによるリダイレクトベースの認証を有効にします。 OpenID Connect または OAuth2 の仕様における 「Authorization Code Flow」 のサポートを有効にします。 implicit-flow-enabled=Implicit Flow の有効 -implicit-flow-enabled.tooltip=OpenID Connect の認可コードなしのリダイレクトベース認証のサポートを有効にします。OpenID Connect または OAuth2 の仕様でいうと、 「Implicit Flow」 のサポートを有効にします。 +implicit-flow-enabled.tooltip=OpenID Connect の認可コードなしのリダイレクトベース認証のサポートを有効にします。OpenID Connect または OAuth2 の仕様における 「Implicit Flow」 のサポートを有効にします。 direct-access-grants-enabled=ダイレクトアクセスグラントの有効 -direct-access-grants-enabled.tooltip=ダイレクトアクセスグラントのサポートを有効にします。これは、アクセストークンの取得のために Keycloak サーバーとユーザーのユーザー名/パスワードで直接アクセスを行います。OAuth2 の仕様でいうと、 「リソースオーナーパスワードクレデンシャルグラント」 のサポートを有効にします。 +direct-access-grants-enabled.tooltip=ダイレクトアクセスグラントのサポートを有効にします。これは、アクセストークンの取得のために Keycloak サーバーとユーザーのユーザー名/パスワードで直接アクセスを行います。OAuth2 の仕様における 「リソースオーナーパスワードクレデンシャルグラント」 のサポートを有効にします。 service-accounts-enabled=サービスアカウントの有効 -service-accounts-enabled.tooltip=Keycloak にこのクライアントを認証し、このクライアント専用のアクセストークンの取得ができるようになります。OAuth2 の仕様でいうと、 「クライアントクレデンシャルグラント」 のサポートを有効にします。 +service-accounts-enabled.tooltip=Keycloak にこのクライアントを認証し、このクライアント専用のアクセストークンの取得ができるようになります。OAuth2 の仕様における 「クライアントクレデンシャルグラント」 のサポートを有効にします。 include-authnstatement=AuthnStatement を含める include-authnstatement.tooltip=認証方式とタイムスタンプを含めたステートメントをログインレスポンスに含めるべきか設定します。 sign-documents=ドキュメントを署名する diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_ja.properties b/themes/src/main/resources/theme/base/admin/messages/messages_ja.properties index b3708dde42..9a8d26db97 100644 --- a/themes/src/main/resources/theme/base/admin/messages/messages_ja.properties +++ b/themes/src/main/resources/theme/base/admin/messages/messages_ja.properties @@ -4,9 +4,9 @@ invalidPasswordMinLowerCaseCharsMessage=無効なパスワード: 少なくと invalidPasswordMinDigitsMessage=無効なパスワード: 少なくとも {0} 文字の数字を含む必要があります。 invalidPasswordMinUpperCaseCharsMessage=無効なパスワード: 少なくとも {0} 文字の大文字を含む必要があります。 invalidPasswordMinSpecialCharsMessage=無効なパスワード: 少なくとも {0} 文字の特殊文字を含む必要があります。 -invalidPasswordNotUsernameMessage=無効なパスワード: ユーザー名と同じは禁止されています。 +invalidPasswordNotUsernameMessage=無効なパスワード: ユーザー名と同じパスワードは禁止されています。 invalidPasswordRegexPatternMessage=無効なパスワード: 正規表現パターンと一致しません。 -invalidPasswordHistoryMessage=無効なパスワード: 最近の {0} パスワードのいずれかと同じは禁止されています。 +invalidPasswordHistoryMessage=無効なパスワード: 最近の {0} パスワードのいずれかと同じパスワードは禁止されています。 ldapErrorInvalidCustomFilter=LDAP フィルターのカスタム設定が、 「(」 から開始または 「)」 で終了となっていません。 ldapErrorMissingClientId=レルムロールマッピングを使用しない場合は、クライアント ID は設定内で提供される必要があります。 diff --git a/themes/src/main/resources/theme/base/login/messages/messages_ja.properties b/themes/src/main/resources/theme/base/login/messages/messages_ja.properties index bf5b8db56f..e0d1fd87d1 100644 --- a/themes/src/main/resources/theme/base/login/messages/messages_ja.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_ja.properties @@ -166,9 +166,9 @@ invalidPasswordMinDigitsMessage=無効なパスワード: 少なくとも {0} invalidPasswordMinLowerCaseCharsMessage=無効なパスワード: 少なくとも {0} 文字の小文字を含む必要があります。 invalidPasswordMinUpperCaseCharsMessage=無効なパスワード: 少なくとも {0} 文字の大文字を含む必要があります。 invalidPasswordMinSpecialCharsMessage=無効なパスワード: 少なくとも {0} 文字の特殊文字を含む必要があります。 -invalidPasswordNotUsernameMessage=無効なパスワード: ユーザー名と同じは禁止されています。 +invalidPasswordNotUsernameMessage=無効なパスワード: ユーザー名と同じパスワードは禁止されています。 invalidPasswordRegexPatternMessage=無効なパスワード: 正規表現パターンと一致しません。 -invalidPasswordHistoryMessage=無効なパスワード: 最近の {0} パスワードのいずれかと同じは禁止されています。 +invalidPasswordHistoryMessage=無効なパスワード: 最近の {0} パスワードのいずれかと同じパスワードは禁止されています。 failedToProcessResponseMessage=応答を処理できませんでした httpsRequiredMessage=HTTPS が必須です From ec5289b5c8e6a8378167d4f14da682ef3a7ac344 Mon Sep 17 00:00:00 2001 From: mwcz Date: Fri, 26 Aug 2016 11:23:44 -0400 Subject: [PATCH 27/49] send cookies along with keycloak.updateToken() --- adapters/oidc/js/src/main/resources/keycloak.js | 1 + 1 file changed, 1 insertion(+) diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index 563f7cacc5..577452e634 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -380,6 +380,7 @@ var req = new XMLHttpRequest(); req.open('POST', url, true); req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + req.withCredentials = true; if (kc.clientId && kc.clientSecret) { req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); From a462dd78bb6be498135b4cc22ef75ad2c61bd758 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Fri, 26 Aug 2016 16:40:34 -0400 Subject: [PATCH 28/49] Update dev doc for keycloak subsystem configuration. --- README.md | 1 + misc/UpdatingServerConfig.md | 65 ++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 misc/UpdatingServerConfig.md diff --git a/README.md b/README.md index c52a172d70..14addf7442 100755 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ Contributing * [Testsuite](misc/Testsuite.md) - Details about testsuite, but also how to quickly run Keycloak during development and a few test tools (OTP generation, LDAP server, Mail server) * [Database Testing](misc/DatabaseTesting.md) - How to do testing of Keycloak on different databases * [Updating Database](misc/UpdatingDatabaseSchema.md) - How to change the Keycloak database + * [Changing the Default keycloak-subsystem Configuration](misc/UpdatingServerConfig.md) - How to update the default keycloak-subsystem config * [Developer Mailing List](https://lists.jboss.org/mailman/listinfo/keycloak-dev) - Mailing list to discuss development of Keycloak diff --git a/misc/UpdatingServerConfig.md b/misc/UpdatingServerConfig.md new file mode 100644 index 0000000000..a3282af996 --- /dev/null +++ b/misc/UpdatingServerConfig.md @@ -0,0 +1,65 @@ +# Changing the Default *keycloak-subsystem* Configuration + +If you need to make a change to the default keycloak-subsystem +configuration that is packaged with our distributions, you will need to edit this file: +https://github.com/keycloak/keycloak/blob/master/wildfly/server-subsystem/src/main/config/default-server-subsys-config.properties + +This file contains a single multi-line property containing the subsystem +xml declaration. Maven filtering is used to read this property and +inject it everywhere it needs to go. Editing this file will also take +care of propagating it to the distributions like server-dist and demo-dist. + +Also, you need to create CLI commands for each change by editing this file: +https://github.com/keycloak/keycloak/blob/master/wildfly/server-subsystem/src/main/resources/cli/default-keycloak-subsys-config.cli + +This CLI snippet is used in the scripts required by the overlay distribution. + +## Updating an SPI +The changes you will likely make are when you need to add a new SPI, change an existing SPI, or add/change a provider within an SPI. + +All elements in an SPI declaration are optional, but a full SPI declaration + looks like this: +````xml + + mongo + + + + + + + + + + + + +```` +Here we have two providers defined for the SPI `dblock`. The +`default-provider` is listed as `mongo`. However it is up to the SPI to decide how it will +treat this setting. Some SPIs allow more than one provider and some do not. So +`default-provider` can help the SPI to choose. + +Also notice that each provider defines its own set of configuration +properties. The fact that both providers above have a property called +`lockWaitTimeout` is just a coincidence. + +## Values of type *List* +The type of each property value is interpreted by the provider. However, +there is one exception. Consider the `jpa` provider for the `eventStore` API: +````xml + + + + + + + +```` +We see that the value begins and ends with square brackets. That means that +the value will be passed to the provider as a list. In this example, +the system will pass the +provider a list with two element values `EVENT1` and `EVENT2`. To add +more values to the list, just separate each list element with a comma. Unfortunately, +you do need to escape the quotes surrounding each list element with +`"`. From 4f51b7b34c9ac737d42fd4d33c46399c7ba295c6 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 29 Aug 2016 09:21:11 +0200 Subject: [PATCH 29/49] KEYCLOAK-3462 Fix exception not displayed in init from KeycloakServer --- .../services/resources/KeycloakApplication.java | 10 +++++++++- .../java/org/keycloak/testsuite/KeycloakServer.java | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 66a643ce13..6cdf52eab2 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -70,6 +70,8 @@ public class KeycloakApplication extends Application { private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + protected boolean embedded = false; + protected Set singletons = new HashSet(); protected Set> classes = new HashSet>(); @@ -78,6 +80,10 @@ public class KeycloakApplication extends Application { public KeycloakApplication(@Context ServletContext context, @Context Dispatcher dispatcher) { try { + if ("true".equals(context.getInitParameter("keycloak.embedded"))) { + embedded = true; + } + loadConfig(context); this.contextPath = context.getContextPath(); @@ -139,7 +145,9 @@ public class KeycloakApplication extends Application { setupScheduledTasks(sessionFactory); } catch (Throwable t) { - exit(1); + if (!embedded) { + exit(1); + } throw t; } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java index 3b8d68b9b2..8806514a70 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java @@ -309,6 +309,8 @@ public class KeycloakServer { di.setDeploymentName("Keycloak"); di.setDefaultEncoding("UTF-8"); + di.addInitParameter("keycloak.embedded", "true"); + di.setDefaultServletConfig(new DefaultServletConfig(true)); ServletInfo restEasyDispatcher = Servlets.servlet("Keycloak REST Interface", HttpServlet30Dispatcher.class); From 539d9863aa001323822bcd1c50a5ca8c57266554 Mon Sep 17 00:00:00 2001 From: Caroline Olsen Date: Mon, 29 Aug 2016 10:48:12 +0200 Subject: [PATCH 30/49] Add Norwegian localization KEYCLOAK-3487 --- .../account/messages/messages_en.properties | 3 +- .../account/messages/messages_no.properties | 163 +++ .../messages/admin-messages_no.properties | 1118 +++++++++++++++++ .../admin/messages/messages_no.properties | 14 + .../email/messages/messages_no.properties | 24 + .../login/messages/messages_en.properties | 3 +- .../login/messages/messages_no.properties | 229 ++++ 7 files changed, 1552 insertions(+), 2 deletions(-) create mode 100644 themes/src/main/resources/theme/base/account/messages/messages_no.properties create mode 100644 themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties create mode 100644 themes/src/main/resources/theme/base/admin/messages/messages_no.properties create mode 100644 themes/src/main/resources/theme/base/email/messages/messages_no.properties create mode 100644 themes/src/main/resources/theme/base/login/messages/messages_no.properties diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties index bdcf43aedb..8ed1de66a4 100755 --- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties @@ -158,5 +158,6 @@ locale_es=Espa\u00F1ol locale_fr=Fran\u00e7ais locale_it=Italian locale_ja=\u65E5\u672C\u8A9E +locale_no=Norsk locale_pt-BR=Portugu\u00EAs (Brasil) -locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 \ No newline at end of file +locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 diff --git a/themes/src/main/resources/theme/base/account/messages/messages_no.properties b/themes/src/main/resources/theme/base/account/messages/messages_no.properties new file mode 100644 index 0000000000..3cae31ef16 --- /dev/null +++ b/themes/src/main/resources/theme/base/account/messages/messages_no.properties @@ -0,0 +1,163 @@ +doSave=Lagre +doCancel=Avbryt +doLogOutAllSessions=Logg ut av alle sesjoner +doRemove=Fjern +doAdd=Legg til +doSignOut=Logg ut + +editAccountHtmlTitle=Rediger konto +federatedIdentitiesHtmlTitle=Federerte identiteter +accountLogHtmlTitle=Kontologg +changePasswordHtmlTitle=Endre passord +sessionsHtmlTitle=Sesjoner +accountManagementTitle=Keycloak kontoadministrasjon +authenticatorTitle=Autentikator +applicationsHtmlTitle=Applikasjoner + +authenticatorCode=Engangskode +email=E-post +firstName=Fornavn +givenName=Fornavn +fullName=Fullt navn +lastName=Etternavn +familyName=Etternavn +password=Passord +passwordConfirm=Bekreftelse +passwordNew=Nytt passord +username=Brukernavn +address=Adresse +street=Gate-/veinavn + husnummer +locality=By +region=Fylke +postal_code=Postnummer +country=Land +emailVerified=E-post bekreftet +gssDelegationCredential=GSS legitimasjonsdelegering + +role_admin=Administrator +role_realm-admin=Administrator for sikkerhetsdomene +role_create-realm=Opprette sikkerhetsdomene +role_view-realm=Se sikkerhetsdomene +role_view-users=Se brukere +role_view-applications=Se applikasjoner +role_view-clients=Se klienter +role_view-events=Se hendelser +role_view-identity-providers=Se identitetsleverand\u00F8rer +role_manage-realm=Administrere sikkerhetsdomene +role_manage-users=Administrere brukere +role_manage-applications=Administrere applikasjoner +role_manage-identity-providers=Administrere identitetsleverand\u00F8rer +role_manage-clients=Administrere klienter +role_manage-events=Administrere hendelser +role_view-profile=Se profil +role_manage-account=Administrere konto +role_read-token=Lese token +role_offline-access=Frakoblet tilgang +role_uma_authorization=Skaffe tillatelser +client_account=Konto +client_security-admin-console=Sikkerhetsadministrasjonskonsoll +client_admin-cli=Kommandolinje-grensesnitt for administrator +client_realm-management=Sikkerhetsdomene-administrasjon +client_broker=Broker + + +requiredFields=Obligatoriske felt +allFieldsRequired=Alle felt m\u00E5 fylles ut + +backToApplication=« Tilbake til applikasjonen +backTo=Tilbake til {0} + +date=Dato +event=Hendelse +ip=IP +client=Klient +clients=Klienter +details=Detaljer +started=Startet +lastAccess=Sist benyttet +expires=Utl\u00F8per +applications=Applikasjoner + +account=Konto +federatedIdentity=Federert identitet +authenticator=Autentikator +sessions=Sesjoner +log=Logg + +application=Applikasjon +availablePermissions=Tilgjengelige rettigheter +grantedPermissions=Innvilgede rettigheter +grantedPersonalInfo=Innvilget personlig informasjon +additionalGrants=Ekstra rettigheter +action=Handling +inResource=i +fullAccess=Full tilgang +offlineToken=Offline token +revoke=Opphev rettighet + +configureAuthenticators=Konfigurerte autentikatorer +mobile=Mobiltelefon +totpStep1=Installer FreeOTP eller Google Authenticator p\u00E5 din enhet. Begge applikasjoner er tilgjengelige p\u00E5 Google Play og Apple App Store. +totpStep2=\u00C5pne applikasjonen og skann strekkoden eller skriv inn koden. +totpStep3=Skriv inn engangskoden gitt av applikasjonen og klikk Lagre for \u00E5 fullf\u00F8re. + +missingUsernameMessage=Vennligst oppgi brukernavn. +missingFirstNameMessage=Vennligst oppgi fornavn. +invalidEmailMessage=Ugyldig e-postadresse. +missingLastNameMessage=Vennligst oppgi etternavn. +missingEmailMessage=Vennligst oppgi e-postadresse. +missingPasswordMessage=Vennligst oppgi passord. +notMatchPasswordMessage=Passordene er ikke like. + +missingTotpMessage=Vennligst oppgi engangskode. +invalidPasswordExistingMessage=Ugyldig eksisterende passord. +invalidPasswordConfirmMessage=Passordene er ikke like. +invalidTotpMessage=Ugyldig engangskode. + +usernameExistsMessage=Brukernavnet finnes allerede. +emailExistsMessage=E-postadressen finnes allerede. + +readOnlyUserMessage=Du kan ikke oppdatere kontoen din ettersom den er skrivebeskyttet. +readOnlyPasswordMessage=Du kan ikke oppdatere passordet ditt ettersom kontoen din er skrivebeskyttet. + +successTotpMessage=Autentikator for mobiltelefon er konfigurert. +successTotpRemovedMessage=Autentikator for mobiltelefon er fjernet. + +successGrantRevokedMessage=Vellykket oppheving av rettighet. + +accountUpdatedMessage=Kontoen din har blitt oppdatert. +accountPasswordUpdatedMessage=Ditt passord har blitt oppdatert. + +missingIdentityProviderMessage=Identitetsleverand\u00F8r er ikke spesifisert. +invalidFederatedIdentityActionMessage=Ugyldig eller manglende handling. +identityProviderNotFoundMessage=Spesifisert identitetsleverand\u00F8r ikke funnet. +federatedIdentityLinkNotActiveMessage=Denne identiteten er ikke lenger aktiv. +federatedIdentityRemovingLastProviderMessage=Du kan ikke fjerne siste federerte identitet ettersom du ikke har et passord. +identityProviderRedirectErrorMessage=Redirect til identitetsleverand\u00F8r feilet. +identityProviderRemovedMessage=Fjerning av identitetsleverand\u00F8r var vellykket. +identityProviderAlreadyLinkedMessage=Federert identitet returnert av {0} er allerede koblet til en annen bruker. +staleCodeAccountMessage=Siden har utl\u00F8pt. Vennligst pr\u00F8v en gang til. +consentDenied=Samtykke avsl\u00E5tt. + +accountDisabledMessage=Konto er deaktivert, kontakt administrator. + +accountTemporarilyDisabledMessage=Konto er midlertidig deaktivert, kontakt administrator eller pr\u00F8v igjen senere. +invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}. +invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sm\u00E5 bokstaver. +invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sifre. +invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} store bokstaver. +invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} spesialtegn. +invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn. +invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster. +invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene. + +locale_ca=Catal\u00E0 +locale_de=Deutsch +locale_en=English +locale_es=Espa\u00F1ol +locale_fr=Fran\u00e7ais +locale_it=Italian +locale_ja=\u65E5\u672C\u8A9E +locale_no=Norsk +locale_pt-BR=Portugu\u00EAs (Brasil) +locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties new file mode 100644 index 0000000000..574eb9156a --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties @@ -0,0 +1,1118 @@ +consoleTitle=Keycloak administrasjonskonsoll + +# Common messages +enabled=Aktivert +name=Navn +displayName=Vis navn +displayNameHtml=HTML vis navn +save=Lagre +cancel=Avbryt +onText=P\u00C5 +offText=AV +client=Klient +clients=Klienter +clear=T\u00F8m +selectOne=Velg en... + +true=True +false=False + +endpoints=Endepunkter + +# Realm settings +realm-detail.enabled.tooltip=Brukere og klienter har kun tilgang til et sikkerhetsdomene hvis det er aktivert +realm-detail.oidc-endpoints.tooltip=Viser konfigurasjonen av endepunkter for OpenID Connect +registrationAllowed=Registrering av bruker +registrationAllowed.tooltip=Aktiver/deaktiver registreringssiden. En lenke for registrering vil v\u00E6re synlig p\u00E5 innloggingssiden. +registrationEmailAsUsername=E-postadresse som brukernavn +registrationEmailAsUsername.tooltip=Dersom registreringssiden er aktivert, vil feltet for brukernavn v\u00E6re skjult fra registreringsskjema og e-postadresse vil bli brukt som brukernavn for nye brukere. +editUsernameAllowed=Rediger brukernavn +editUsernameAllowed.tooltip=Dersom aktivert, er feltet for brukernavn redigerbart, ellers kun lesbart. +resetPasswordAllowed=Glemt passord +resetPasswordAllowed.tooltip=Vis en lenke p\u00E5 innloggingssiden som brukere kan klikke p\u00E5 om de har glemt sine innloggingsdetaljer. +rememberMe=Husk meg +rememberMe.tooltip=Vis en avkryssingsboks p\u00E5 innloggingssiden som lar brukere forbli innlogget mellom omstart av nettleser og inntil sesjonen utl\u00F8per. +verifyEmail=Bekreft e-postadresse +verifyEmail.tooltip=Krev at bruker verifiserer sin e-postadresse f\u00F8rste gang de logger inn. +sslRequired=Krev SSL +sslRequired.option.all=Alle foresp\u00F8rsler +sslRequired.option.external=Eksterne foresp\u00F8rsler +sslRequired.option.none=Ingen +sslRequired.tooltip=Kreves HTTPS? 'Ingen' betyr at HTTPS ikke kreves for noen klienters IP-adresse. 'Ekstern foresp\u00F8rsel' betyr at localhost og private IP-adresser kan f\u00E5 tilgang uten HTTPS. 'Alle foresp\u00F8rsler' betyr at HTTPS kreves for alle IP-adresser. +publicKey=Offentlig n\u00F8kkel +privateKey=Privat n\u00F8kkel +gen-new-keys=Generer nye n\u00F8kler +certificate=Sertifikat +host=Vert +smtp-host=SMTP Vert +port=Port +smtp-port=SMTP Port (standard er 25) +from=Fra +sender-email-addr=Senders e-postadresse +enable-ssl=Aktiver SSL +enable-start-tls=Aktiver StartTLS +enable-auth=Aktiver autentisering +username=Brukernavn +login-username=Innloggingsbrukernavn +password=Passord +login-password=Innloggingspassord +login-theme=Innloggingstema +login-theme.tooltip=Velg tema for sidene: innlogging, TOTP, rettigheter, registrering, glemt passord. +account-theme=Kontotema +account-theme.tooltip=Velg tema for brukerkontoadministrasjonssider. +admin-console-theme=Administrasjonskonsolltema +select-theme-admin-console=Velg et tema for administrasjonskonsollen. +email-theme=E-posttema +select-theme-email=Velg tema for e-post sendt av server. +i18n-enabled=Internasjonalisering aktivert +supported-locales=St\u00F8ttede lokaliteter +supported-locales.placeholder=Skriv inn en lokalitet og klikk enter +default-locale=Standard lokalitet +realm-cache-clear=Cache for sikkerhetsdomenet +realm-cache-clear.tooltip=T\u00F8m sikkerhetsdomenecache (Dette vil fjerne oppf\u00F8ringer for alle sikkerhetsdomener) +user-cache-clear=Brukercache +user-cache-clear.tooltip=T\u00F8m brukercache (Dette vil fjerne oppf\u00F8ringer for alle sikkerhetsdomener) +revoke-refresh-token=Fjern refresh token +revoke-refresh-token.tooltip=Hvis aktivert kan refresh token kun bli brukt en gang. Ellers vil refresh tokens kunne bli brukt flere ganger. +sso-session-idle=Inaktiv SSO sesjon +seconds=Sekunder +minutes=Minutter +hours=Timer +days=Dager +sso-session-max=Maksimum SSO sesjon +sso-session-idle.tooltip=Tiden en sesjon er tillatt \u00E5 v\u00E6re inaktiv f\u00F8r den utl\u00F8per. Tokens og nettlesersesjoner vil bli ugyldig n\u00E5r en sesjon utl\u00F8per. +sso-session-max.tooltip=Maksimum tid f\u00F8r en sesjon utl\u00F8per. Tokens og nettlesersesjoner vil bli ugyldig n\u00E5r en sesjon utl\u00F8per. +offline-session-idle=Inaktiv sesjon i frakoblet modus +offline-session-idle.tooltip=Tiden en sesjon i frakoblet modus er tillatt \u00E5 v\u00E6re inaktiv f\u00F8r den utl\u00F8per. Du m\u00E5 bruke tokens for frakoblet modus for \u00E5 oppdatere sesjonen minst en gang i denne perioden, ellers vil sesjonen utl\u00F8pe. +access-token-lifespan=Levetid for access token +access-token-lifespan.tooltip= Maksimum tid f\u00F8r et access token utl\u00F8per. Det anbefales at denne verdien er kort i forhold til SSO timeout. +access-token-lifespan-for-implicit-flow=Access token-levetid for implicit flow +access-token-lifespan-for-implicit-flow.tooltip=Maksimum tid f\u00F8r et access token utstedt under OpenID Connect implicit flow utl\u00F8per. Det anbefales at denne er kortere enn SSO timeout. Det er ingen mulighet til \u00E5 oppdatere tokenet i l\u00F8pet av en implicit flow, derfor er det satt en separat timeout som er forskjellig fra 'Levetid for Access Token'. +client-login-timeout=Timeout av klientinnlogging +client-login-timeout.tooltip=Maksimum tid en klient har for \u00E5 fullf\u00F8re access token protokollen. Dette burde normalt v\u00E6re 1 minutt. +login-timeout=Timeout for innlogging +login-timeout.tooltip=Maksimum tid en bruker har til \u00E5 fullf\u00F8re en innlogging. Det anbefales at denne er relativt lang. 30 minutter eller mer. +login-action-timeout=Timeout for innloggingshandling. +login-action-timeout.tooltip=Maksimum tid en bruker har til \u00E5 fullf\u00F8re handlinger relatert til innlogging, som \u00E5 oppdatere passord eller konfigurere TOTP. Det anbefales at denne er relativt lang. 5 minutter eller mer. +headers=Headere +brute-force-detection=Deteksjon av Brute Force +x-frame-options=Alternativer for X-Frame +x-frame-options-tooltip=Standardverdi hindrer sider fra \u00E5 bli inkludert via non-origin iframes. (Klikk p\u00E5 etikett for mer informasjon) +content-sec-policy=Sikkerhetspolicy for innhold +content-sec-policy-tooltip=Standardverdi hindrer sider fra \u00E5 bli inkludert via non-origin iframes. (Klikk p\u00E5 etikett for mer informasjon) +content-type-options=Alternativer for X-innholdstyper +content-type-options-tooltip=Standardverdi som forhindrer Internet Explorer og Google Chrome fra \u00E5 MIME-sniffe et svar vekk fra den deklarerte innholdstypen (content-type) (Klikk p\u00E5 etikett for mer informasjon) +max-login-failures=Maksimum antall innloggingsfeil +max-login-failures.tooltip=Hvor mange feil f\u00F8r ventetid blir aktivert. +wait-increment=\u00F8kning av ventetid +wait-increment.tooltip=N\u00E5r terskelen for feil er n\u00E5dd, hvor lenge skal brukeren stenges ute? +quick-login-check-millis=Hurtig innlogging - kontroll av millisekunder +quick-login-check-millis.tooltip=Hvis en feil skjer for raskt samtidig, steng brukeren ute. +min-quick-login-wait=Minimum ventetid for hurtig innlogging +min-quick-login-wait.tooltip=Ventetid etter en hurtig innloggingsfeil. +max-wait=Maksimum ventetid +max-wait.tooltip=Maksimum tid en bruker vil v\u00E6re stengt ute. +failure-reset-time=Tid for tilbakestilling av feil. +failure-reset-time.tooltip=N\u00E5r vil teller for feil nullstilles? +realm-tab-login=Innlogging +realm-tab-keys=N\u00F8kler +realm-tab-email=E-post +realm-tab-themes=Tema +realm-tab-cache=Cache +realm-tab-tokens=Tokens +realm-tab-client-initial-access=F\u00F8rste access token +realm-tab-security-defenses=Sikkerhetsmekanismer +realm-tab-general=Generelt +add-realm=Legg til sikkerhetsdomene + +#Session settings +realm-sessions=Sikkerhetsdomenesesjoner +revocation=Oppheving +logout-all=Logg ut alle +active-sessions=Aktive sesjoner +sessions=Sesjoner +not-before=Ikke f\u00F8r +not-before.tooltip=Opphev alle tokens utstedt f\u00F8r denne datoen. +set-to-now=Sett til n\u00E5 +push=Send +push.tooltip=For enhver klient som har en administratorURL, send dem beskjed om den nye opphevingspolicyen. + +#Protocol Mapper +usermodel.prop.label=Egenskap +usermodel.prop.tooltip=Navn p\u00E5 egenskapsmetoden i UserModel-grensesnittet. For eksempel, en verdi av 'e-post' vil referere til metoden UserModel.getEmail(). +usermodel.attr.label=Brukerattributt +usermodel.attr.tooltip=Navn p\u00E5 lagret brukerattributt som er navnet p\u00E5 en attributt innenfor UserModel.attribute map. +userSession.modelNote.label=Brukersesjonsmerknad +userSession.modelNote.tooltip=Navn p\u00E5 lagret brukersesjonsmerknad innenfor UserSessionModel.note map. +multivalued.label=Flere verdier +multivalued.tooltip=Angir om en attributt st\u00F8tter flere verdier. Hvis true, vil listen med alle verdier for dette attributtet bli satt som claims. Hvis false, vil bare den f\u00F8rste verdien bli satt som claim. +selectRole.label=Velg rolle +selectRole.tooltip=Skriv inn rolle i tekstboksen til venstre, eller klikk p\u00E5 denne knappen for \u00E5 bla gjennom og velge rollen du \u00F8nsker. +tokenClaimName.label=Navn p\u00E5 token claim +tokenClaimName.tooltip=Navn p\u00E5 claim som skal legges inn i token. Denne kan v\u00E6re et fullt kvalifisert navn som 'address.street'. I dette tilfellet vil et nestet jsonobjekt bli laget. +jsonType.label=JSON-type for claims +jsonType.tooltip=JSON-type som burde bli brukt for \u00E5 fylle json claimet i tokenet. long, int, boolean og String er gyldige verdier. +includeInIdToken.label=Legg til i ID token +includeInIdToken.tooltip=Burde claim bli lagt til i ID token? +includeInAccessToken.label=Legg til i access token +includeInAccessToken.tooltip=Burde claim bli lagt til i access token? +includeInUserInfo.label=Legg til i brukerinfo +includeInUserInfo.tooltip=Burde claim bli lagt til i brukerinfo? +usermodel.clientRoleMapping.clientId.label=Klient-ID +usermodel.clientRoleMapping.clientId.tooltip=Klient-ID for \u00E5 mappe roller +usermodel.clientRoleMapping.rolePrefix.label=Prefiks for klientrolle +usermodel.clientRoleMapping.rolePrefix.tooltip=Prefiks for hver klientrolle (valgfri). +usermodel.realmRoleMapping.rolePrefix.label=Prefiks for sikkerhetsdomenerolle +usermodel.realmRoleMapping.rolePrefix.tooltip=Prefiks for hver sikkerhetsdomenerolle (valgfri). + +# client details +clients.tooltip=Klienter er betrodde nettleserapplikasjoner og web-tjenester i et sikkerhetsdomene. Disse klientene kan be om en innlogging. Du kan ogs\u00E5 definere klientspesifikke roller. +search.placeholder=S\u00F8k... +create=Opprett +import=Importer +client-id=Klient-ID +base-url=Base URL +actions=Handlinger +not-defined=Ikke definert +edit=Rediger +delete=Slett +no-results=Ingen resultater +no-clients-available=Ingen klienter er tilgjengelige +add-client=Legg til klient +select-file=Velg fil +view-details=Se detaljer +clear-import=T\u00F8m import +client-id.tooltip=Angir ID referert i URI og tokens. For eksempel 'min-klient'. For SAML er dette ogs\u00E5 forventet utgiververdi fra authn-foresp\u00F8rsler +client.name.tooltip=Angir klientnavnet som blir vist. For eksempel, 'Min klient'. St\u00F8tter n\u00F8kler for lokaliserte verdier. For eksempel\: ${my_client} +client.enabled.tooltip=Deaktiverte klienter kan ikke initiere en innlogging eller motta access tokens. +consent-required=Samtykke p\u00E5krevd +consent-required.tooltip=Hvis aktivert m\u00E5 brukere gi samtykke for at klienten skal f\u00E5 tilgang. +client-protocol=Klientprotokoll +client-protocol.tooltip='OpenID connect' tillater klienter \u00E5 verifisere identiteten til sluttbrukeren basert p\u00E5 autentisering utf\u00F8rt av en autorisasjonsserver. 'SAML' aktiverer en web-basert autentisering og autoriseringsscenarier som inkluderer cross-domain single sign-on (SSO) og som bruker security tokens som inneholder assertions for \u00E5 dele informasjon videre. +access-type=Tilgangstype +access-type.tooltip='Confidential' klienter krever en secret for \u00E5 initiere innloggingsprotokoll. 'Public' klienter krever ikke en secret. 'Bearer-only' klienter er webtjenester som aldri initierer en innlogging. +standard-flow-enabled=Standard flow aktivert +standard-flow-enabled.tooltip=Dette aktiverer standard OpenID Connect redirect-basert autentisering med autorisasjonskode. I forhold til OpenID Connect eller OAuth2 spesifikasjoner aktiverer dette st\u00F8tte for 'Authorization Code Flow' for denne klienten. +implicit-flow-enabled=Implicit flow aktivert +implicit-flow-enabled.tooltip=Dette aktiverer st\u00F8tte for OpenID Connect redirect-basert autentisering uten autorisasjonskode. I forhold til OpenID Connect eller OAuth2 spesifikasjoner aktiverer dette st\u00F8tte for 'Implicit Flow' for denne klienten. +direct-access-grants-enabled=Direct access grants aktivert +direct-access-grants-enabled.tooltip=Dette gir st\u00F8tte for Direct Access Grants, som betyr at klienten har tilgang til brukerens brukernavn/passord og kan bytte dette direkte med Keycloak-serveren for access token. I f\u00F8lge OAuth2 spesifikasjonen, aktiverer dette st\u00F8tte for 'Resource Owner Password Credentials Grant' for denne klienten. +service-accounts-enabled=Tjenestekonto aktivert +service-accounts-enabled.tooltip=Lar deg autentisere denne klienten til Keycloak og hente access token dedikert til denne klienten. I f\u00F8lge OAuth2 spesifikasjonen, aktiverer dette st\u00F8tte for 'Client Credentials Grant' for denne klienten. +include-authnstatement=Inkluder AuthnStatement +include-authnstatement.tooltip=Skal et statement som spesifiserer metoden for tidsstempel inng\u00E5 i innloggingssvaret? +sign-documents=Signer dokumenter +sign-documents.tooltip=Skal SAML dokumenter bli signert av sikkerhetsdomenet? +sign-assertions=Signer assertions +sign-assertions.tooltip=Skal assertions i SAML dokumenter bli signert? Denne innstillingen er ikke n\u00F8dvendig hvis et dokument allerede har blitt signert. +signature-algorithm=Signaturalgoritme +signature-algorithm.tooltip=Signaturalgoritmen som brukes for \u00E5 signere et dokument. +canonicalization-method=Kanoniseringsmetode +canonicalization-method.tooltip=Kanoniseringsmetode for XML signaturer. +encrypt-assertions=Krypter assertions +encrypt-assertions.tooltip=Skal SAML assertions bli kryptert med klientens offentlige n\u00F8kkel ved \u00E5 bruke AES? +client-signature-required=Klientens signatur er p\u00E5krevd +client-signature-required.tooltip=Skal klienten signere sine SAML foresp\u00F8rsler og svar? Og skal de valideres? +force-post-binding=Force POST binding +force-post-binding.tooltip=Bruk alltid POST binding for svar. +front-channel-logout=Front channel utlogging +front-channel-logout.tooltip=Hvis satt til true, krever utlogging en redirect i nettleser til klient. Hvis satt til false, vil server utf\u00F8re en bakgrunnskall for utlogging. +force-name-id-format=Force navn-ID format +force-name-id-format.tooltip=Ignorer forespurt format p\u00E5 Navn-ID emnet og bruk den som er konfigurert i administrasjonskonsollen. +name-id-format=Navn-ID format +name-id-format.tooltip=Navn-ID formatet som skal brukes for emnet. +root-url=Root URL +root-url.tooltip=Root URL lagt til relative URLer +valid-redirect-uris=Gyldig redirect URIer +valid-redirect-uris.tooltip=Gyldig URI m\u00F8nster som en nettleser kan redirecte til etter en vellykket innlogging eller utlogging. Enkle jokertegn er tillatt, for eksempel 'http://example.com/*'. Relativ sti kan ogs\u00E5 spesifiseres, for eksempel /my/relative/path/*. Relative stier er relative til klientens root URL, eller hvis ingen er spesifisert brukes root URL for autorisasjonsserveren. For SAML m\u00E5 du sette et gyldig URI m\u00F8nster hvis du er avhengig av at URL for forbrukertjenesten er integrert med foresp\u00F8rselen for p\u00E5logging. +base-url.tooltip=Standard URL som kan brukes n\u00E5r autorisasjonsserveren trenger \u00E5 redirecte eller lenke tilbake til klienten. +admin-url=Admin URL +admin-url.tooltip=URL til administratorgrensesnitt for klienten. Sett denne hvis klienten st\u00F8tter adapter REST API. Dette REST APIet tillater autorisasjonsserveren til \u00E5 sende tilbakekallingsregler og andre administrative oppgaver. Vanligvis er dette satt til klientens base URL. +master-saml-processing-url=Master SAML prosesserings URL +master-saml-processing-url.tooltip=Hvis konfigurert vil denne URLen bli brukt for hver binding til b\u00E5de SPs Assertion Consumer og Single Logout-tjenester. Denne kan bli individuelt overstyrt for hver binding og tjenester i konfigurasjonen for finkornet SAML endepunkt. +idp-sso-url-ref=IDP initiert SSO URL navn +idp-sso-url-ref.tooltip=Navn p\u00E5 URL-fragment som refererer til klienten n\u00E5r du vil gj\u00F8re en IDP initiert SSO. La denne st\u00E5 tom om du \u00F8nsker \u00E5 deaktivere IDP initiert SSO. URLen vil v\u00E6re: {server-root}/realms/{realm}/protocol/saml/clients/{client-url-name} +idp-sso-relay-state=IDP initiert SSO relay state +idp-sso-relay-state.tooltip=Relay state du \u00F8nsker \u00E5 sende med SAML foresp\u00F8rselen n\u00E5r du vil utf\u00F8re en IDP initiert SSO. +web-origins=Web origins +web-origins.tooltip=Tillat CORS origins. For \u00E5 tillate alle origins med gyldig Redirect URIer legg til '+'. For \u00E5 tillate alle origins legg til '*'. +fine-saml-endpoint-conf=Finkornet SAML endepunktskonfigurasjon +fine-saml-endpoint-conf.tooltip=Utvid denne delen til \u00E5 konfigurere n\u00F8yaktige URLer for Assertion Consumer og Single Logout-tjeneste. +assertion-consumer-post-binding-url=Assertion consumer service POST binding URL +assertion-consumer-post-binding-url.tooltip=SAML POST binding URL for klientens assertion customer service (innloggingsrespons). Du kan la denne st\u00E5 tom om du ikke \u00F8nsker en URL for denne bindingen. +assertion-consumer-redirect-binding-url=Assertion Consumer Service redirect binding URL +assertion-consumer-redirect-binding-url.tooltip=SAML redirect for klientens assertion consumer service (innloggingsrespons). Du kan la denne st\u00E5 tom om du ikke \u00F8nsker en URL for denne bindingen. +logout-service-binding-post-url=logout-tjeneste POST binding URL +logout-service-binding-post-url.tooltip=SAML POST binding URL for klientens single logout-tjeneste. Du kan la dette st\u00E5 tomt om du bruker en annen binding. +logout-service-redir-binding-url=Logout-tjeneste redirect binding URL +logout-service-redir-binding-url.tooltip=SAML redirect binding URL for klientens single logout-tjeneste. Du kan la dette st\u00E5 tomt om du bruker en annen binding. + +# client import +import-client=Importer klient +format-option=Formatalternativer +select-format=Velg et format +import-file=Importer fil + +# client tabs +settings=Innstillinger +credentials=Innloggingsdetaljer +saml-keys=SAML n\u00F8kler +roles=Roller +mappers=Mappere +mappers.tooltip=Protokollmappere som utf\u00F8rer endringer av tokens og dokumenter. De kan utf\u00F8re handlinger som \u00E5 mappe brukerdata til protokollclaims, eller bare endre foresp\u00F8rsler som blir sendt mellom klienten og autorisasjonsserver. +scope=Scope +scope.tooltip=Mapping av scope lar deg begrense hvilke rollemappinger som er inkludert i access token som klienten har bedt om. +sessions.tooltip=Viser aktive sesjoner for denne klienten. Tillater deg \u00E5 se hvilke brukere som er aktive og n\u00E5r de logget inn. +offline-access=Frakoblet tilgang +offline-access.tooltip=Viser frakoblede sesjoner for denne klienten. Tillater deg \u00E5 se hvilke brukere som henter tokens for frakoblet modus og n\u00E5r de henter den. For \u00E5 tilbakekalle alle tokens for klienten, g\u00E5 til fanen Opphev og sett verdien for Ikke f\u00F8r til n\u00E5. +clustering=Clustering +installation=Installasjon +installation.tooltip=Verkt\u00F8y for \u00E5 generere ulike konfigurasjonsformater for klientadapter som du kan laste ned eller klippe og lime inn for \u00E5 konfigurere klientene dine. +service-account-roles=Tjenestekonto-roller +service-account-roles.tooltip=Tillater deg \u00E5 autentisere rollemappinger for tjenestekontoen som er dedikert til denne klienten. + +# client credentials +client-authenticator=Klientautentikator +client-authenticator.tooltip=Klientautentikator som blir brukt for \u00E5 autentisere denne klienten mot keycloak-server +certificate.tooltip=Klientsertifikat for \u00E5 validere JWT utstedt av klienten og signert av privatn\u00F8kkel til klient fra din keystore. +publicKey.tooltip=Offentlig n\u00F8kkel for \u00E5 validere JWT utstedt av klient og signert av klientens privatn\u00F8kkel. +no-client-certificate-configured=Ingen klientsertifikat er konfigurert +gen-new-keys-and-cert=Generer nye n\u00F8kler og sertifikater +import-certificate=Importer sertifikat +gen-client-private-key=Generer privatn\u00F8kkel for klient +generate-private-key=Generer privatn\u00F8kkel +archive-format=Arkivformat +archive-format.tooltip=Java keystore eller PKCS12 arkivformat. +key-alias=N\u00F8kkelalias +key-alias.tooltip=Arkiv-alias for din privatn\u00F8kkel og sertifikater. +key-password=N\u00F8kkelpassord +key-password.tooltip=Passord for \u00E5 f\u00E5 tilgang til privatn\u00F8kler i arkivet +store-password=Lagre passord +store-password.tooltip=Passord for \u00E5 f\u00E5 tilgang til arkivet +generate-and-download=Generer og Last ned +client-certificate-import=Import av klientsertifikat +import-client-certificate=Importer klientsertifikat +jwt-import.key-alias.tooltip=Arkiv-alias for sertifikatet ditt. +secret=Secret +regenerate-secret=Regenere secret +registrationAccessToken=Access token for registrering +registrationAccessToken.regenerate=Regenerer access token for registrering +registrationAccessToken.tooltip=Access token for registrering gir klienter tilgang til registreringstjenesten for klienter. +add-role=Legg til rolle +role-name=Rollenavn +composite=Sammensatt +description=Beskrivelse +no-client-roles-available=Ingen klientroller er tilgjengelig +scope-param-required=Scope parameter p\u00E5krevd +scope-param-required.tooltip=Denne rollen vil kun bli gitt hvis parameter for scope med rollenavn blir brukt under foresp\u00F8rsel av autorisasjon/token. +composite-roles=Sammensatte roller +composite-roles.tooltip=N\u00E5r denne rollen er tildelt/ikke tildelt til en bruker, vil hvilken som helst rolle assosiert med denne bli implisitt tildelt/ikke tildelt. +realm-roles=Sikkerhetsdomeneroller +available-roles=Tilgjengelig roller +add-selected=Legg til valgte +associated-roles=Assosierte roller +composite.associated-realm-roles.tooltip=Sikkerhetsdomeneniv\u00E5-rolle knyttet til denne sammensatte rollen. +composite.available-realm-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller knyttet til denne sammensatte rollen. +remove-selected=Fjern valgte +client-roles=Klientroller +select-client-to-view-roles=Velg klient for \u00E5 se roller for klient +available-roles.tooltip=Roller fra denne klienten som du kan knytte til denne sammensatte rollen. +client.associated-roles.tooltip=Klientrolle assosiert med denne sammensatte rollen. +add-builtin=Legg til Builtin +category=Kategori +type=Type +no-mappers-available=Ingen mappere er tilgjengelig +add-builtin-protocol-mappers=Legg til Builtin protokollmappere +add-builtin-protocol-mapper=Legg til Builtin protokollmapper +scope-mappings=Scopemapping +full-scope-allowed=Tillatt med fullt scope +full-scope-allowed.tooltip=Lar deg \u00E5 deaktivere alle restriksjoner. +scope.available-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller som kan bli tildelt til scope. +assigned-roles=Tildelte roller +assigned-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller tildelt til scope. +effective-roles=Effektive roller +realm.effective-roles.tooltip=Tildelte sikkerhetsdomeneniv\u00E5-roller som kan ha blitt arvet fra en sammensatt rolle. +select-client-roles.tooltip=Velg en klient for \u00E5 se roller for klient +assign.available-roles.tooltip=Klientroller som er tilgjengelige til \u00E5 bli tildelt. +client.assigned-roles.tooltip=Tildelte klientroller. +client.effective-roles.tooltip=Tildelte klientroller som kan ha blitt arvet fra en sammensatt rolle. +basic-configuration=Basiskonfigurasjon +node-reregistration-timeout=Timeout for re-registrering av node +node-reregistration-timeout.tooltip=Intervall for \u00E5 angi maksimum tid for registrerte klienters clusternoder for \u00E5 re-registreres. Hvis en clusternode ikke sender re-regisreringsforesp\u00F8rsel til Keycloak innen dette intervallet, vil den bli uregistrert fra Keycloak. +registered-cluster-nodes=Registrerte clusternoder +register-node-manually=Register node manuelt +test-cluster-availability=Test cluster tilgjengelighet +last-registration=Siste registrering +node-host=Nodevert +no-registered-cluster-nodes=Ingen registrerte clusternoder tilgjengelig +cluster-nodes=Clusternoder +add-node=Legg til node +active-sessions.tooltip=Totalt antall aktive brukersesjoner for denne klienten. +show-sessions=Vis sesjoner +show-sessions.tooltip=Advarsel, dette er en potensielt kostbar operasjon avhengig av antall aktive sesjoner. +user=Bruker +from-ip=Fra IP +session-start=Start av sesjon +first-page=F\u00F8rste side +previous-page=Forrige side +next-page=Neste side +client-revoke.not-before.tooltip=Opphev alle token utstedt f\u00F8r denne datoen for denne klienten. +client-revoke.push.tooltip=Hvis administrator URL er konfigurert for denne klienten, dytt denne policyen p\u00E5 denne klienten. +select-a-format=Velg et format +download=Last ned +offline-tokens=Offline tokens +offline-tokens.tooltip=Totalt antall offline tokens for denne klienten. +show-offline-tokens=Vis offline tokens +show-offline-tokens.tooltip=Advarsel, dette er en potensielt kostbar operasjon avhengig av antall offline tokens. +token-issued=Utgitt token +last-access=Sist aksessert +last-refresh=Siste refresh +key-export=Eksporter n\u00F8kkel +key-import=Importer n\u00F8kkel +export-saml-key=Eksporter SAML n\u00F8kkel +import-saml-key=Importer SAML n\u00F8kkel +realm-certificate-alias=Alias for sikkerhetsdomenesertifikat +realm-certificate-alias.tooltip=Sertifikat for sikkerhetsdomenet er ogs\u00E5 lagret i arkivet. Dette er aliaset. +signing-key=Signeringsn\u00F8kkel +saml-signing-key=SAML signeringsn\u00F8kkel +private-key=Privatn\u00F8kkel +generate-new-keys=Generer nye n\u00F8kler +export=Eksporter +encryption-key=Krypteringsn\u00F8kkel +saml-encryption-key.tooltip=SAML krypteringsn\u00F8kkel. +service-accounts=Tjenestekonto-konto +service-account.available-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller som kan bli tildelt til tjeneste-konto. +service-account.assigned-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller tildelt service-konto. +service-account-is-not-enabled-for=Tjeneste-konto er ikke aktivert for {{client}} +create-protocol-mappers=Opprett protokollmappere +create-protocol-mapper=Opprett protokollmapper +protocol=Protokoll +protocol.tooltip=Protokoll... +id=ID +mapper.name.tooltip=Navn p\u00E5 mapper. +mapper.consent-required.tooltip=Ved tildeling av midlertidig tilgang m\u00E5 brukeren samtykke til \u00E5 gi denne informasjonen til klienten? +consent-text=Samtykketekst +consent-text.tooltip=Tekst som blir vist p\u00E5 side for samtykke. +mapper-type=Mappertype +mapper-type.tooltip=Type mapper +# realm identity providers +identity-providers=Identitetsleverand\u00F8r +table-of-identity-providers=Liste over identitetsleverand\u00F8rer +add-provider.placeholder=Legg til leverand\u00F8r... +provider=Leverand\u00F8r +gui-order=Rekkef\u00F8lge for brukergrensesnitt +first-broker-login-flow=Flyt for f\u00F8rste innlogging +post-broker-login-flow=Post-p\u00E5loggingsflyt +redirect-uri=Redirect URI +redirect-uri.tooltip=Redirect URI som skal brukes n\u00E5r du konfigurerer identitetsleverand\u00F8ren. +alias=Alias +identity-provider.alias.tooltip=Aliaset identifiserer en identitetsleverand\u00F8r og kan brukes for \u00E5 bygge en redirect uri. +identity-provider.enabled.tooltip=Aktiver/deaktiver denne identitetsleverand\u00F8ren. +authenticate-by-default=Autentiser som standard +identity-provider.authenticate-by-default.tooltip=Indikerer om en leverand\u00F8r burde fors\u00F8kes \u00E5 brukes om standard for autentisering selv om innloggingssiden enda ikke har blitt vist. +store-tokens=Lagre Tokens +identity-provider.store-tokens.tooltip=Aktiver/deaktiver hvis tokens m\u00E5 bli lagret etter at brukere har blitt autentisert. +stored-tokens-readable=Lagrede lesbare tokens +identity-provider.stored-tokens-readable.tooltip=Aktiver/deaktiver hvis nye brukere kan lese lagrede tokens. Dette tildeles broker.read-token rollen. +update-profile-on-first-login=Oppdater profil ved f\u00F8rste innlogging +on=P\u00E5 +on-missing-info=Ved manglende informasjon +off=Av +update-profile-on-first-login.tooltip=Definer vilk\u00E5rene en bruker m\u00E5 oppfylle for \u00E5 oppdatere sin profil ved f\u00F8rste innlogging. +trust-email=Stol p\u00E5 e-post +trust-email.tooltip=Hvis aktivert vil ikke e-post levert av denne leverand\u00F8ren bli verifisert selv om verifisering er aktivert for sikkerhetsdomenet. +gui-order.tooltip=Antall som angir rekkef\u00F8lgen av leverand\u00F8rer i brukergrensesnittet (For eksempel p\u00E5 innloggingssiden). +first-broker-login-flow.tooltip=Alias for autentiseringsflyt, som trigges etter f\u00F8rste innlogging med denne identitetsleverand\u00F8ren. Begrepet 'F\u00F8rste innlogging' betyr at det enn\u00E5 ikke eksisterer en Keycloak-konto koblet til den autentiserte kontoen til identitetsleverand\u00F8ren. +post-broker-login-flow.tooltip=Alias for autentiseringsflyt, som trigges etter hver innlogging med denne identitetsleverand\u00F8ren. Nyttig om man \u00F8nsker tilleggsverifikasjon av hver bruker autentisert med denne identitetsleverand\u00F8ren (for eksempel med engangskode/engangspassord). La denne st\u00E5 tom om du ikke \u00F8nsker at tilleggautentisering skal bli trigget etter en innlogging med denne identitetsleverand\u00F8ren. Merk ogs\u00E5 at implementasjonen av denne autentikatoren m\u00E5 anta at bruker allerede er satt i ClientSession ettersom identitetsleverand\u00F8ren allerede setter det. +openid-connect-config=OpenID Connect konfigurasjon +openid-connect-config.tooltip=OIDC SP og ekstern IDP-konfigurasjon. +authorization-url=Autorisasjons URL +authorization-url.tooltip=Autorisasjons URLen. +token-url=Token URL +token-url.tooltip=Token URLen. +logout-url=Utloggings URL +identity-provider.logout-url.tooltip=Endepunkt for avsluttende sesjon som brukes for \u00E5 logge ut bruker fra ekstern IDP. +backchannel-logout=Backchannel utlogging +backchannel-logout.tooltip=St\u00F8tter ekstern IDP backchannel utlogging? +user-info-url=Brukerinfo URL +user-info-url.tooltip=Brukerinfo URLen. Denne er valgfri. +identity-provider.client-id.tooltip=Klienten eller klientidentifikator registrert hos identitetsleverand\u00F8ren. +client-secret=Klient secret +show-secret=Vis secret +hide-secret=Skjul secret +client-secret.tooltip=Klienten eller klient secret registrert hos identitetsleverand\u00F8ren. +issuer=Utgiver +issuer.tooltip=Identifikator for utgiver av foresp\u00F8rselen. Hvis dette ikke er oppgitt vil ingen validering utf\u00F8res. +default-scopes=Standard Scopes +identity-provider.default-scopes.tooltip=Scopes som sendes n\u00E5r du ber om autorisasjon. Dette kan v\u00E6re en liste med scopes separert med mellomrom. Standard er satt til 'openid'. +prompt=Prompt +unspecified.option=uspesifisert +none.option=Ingen +consent.option=samtykke +login.option=Innlogging +select-account.option=velg_konto (select_account) +prompt.tooltip=Spesifiserer om autorisasjonsserver skal be sluttbruker om re-autentisering og samtykke. +validate-signatures=Valider signaturer +identity-provider.validate-signatures.tooltip=Aktiver/deaktiver signaturvalidering av eksterne IDP signaturer. +validating-public-key=Valider offentlig n\u00F8kkel +identity-provider.validating-public-key.tooltip=PEM format for offentlig n\u00F8kkel som m\u00E5 brukes for \u00E5 kontrollere eksterne IDP signaturer. +import-external-idp-config=Importer ekstern IDP konfigurasjon +import-external-idp-config.tooltip=Lar deg laste inn ekstern IDP metadata fra en konfigurasjonsfil eller ved \u00E5 laste det ned fra en URL. +import-from-url=Importer fra URL +identity-provider.import-from-url.tooltip=Importer metadata fra et eksternt IDP discovery descriptor. +import-from-file=Importer fra fil +identity-provider.import-from-file.tooltip=Importer metadata fra en nedlastet IDP discovery descriptor. +saml-config=SAML konfigurasjon +identity-provider.saml-config.tooltip=SAML SP og ekstern IDP konfigurasjon. +single-signon-service-url=Single sign-on service URL +saml.single-signon-service-url.tooltip=URL som m\u00E5 brukes for \u00E5 sende autentiseringsforesp\u00F8rsler (SAML AuthnRequest). +single-logout-service-url=Single utloggingstjeneste URL +saml.single-logout-service-url.tooltip=URL som m\u00E5 brukes for \u00E5 sende utloggingsforesp\u00F8rsler. +nameid-policy-format=Policy for nameid-format +nameid-policy-format.tooltip=Angir URI-referanse som tilsvarer et format for identifikator-navn. Standard er satt til urn:oasis:names:tc:SAML:2.0:nameid-format:persistent. +http-post-binding-response=HTTP-POST binding svar +http-post-binding-response.tooltip=Indikerer om man skal svare p\u00E5 foresp\u00F8rsler som bruker HTTP-POST binding eller ikke. Hvis satt til false, vil HTTP-REDIRECT binding bli brukt. +http-post-binding-for-authn-request=HTTP-POST binding for AuthnRequest +http-post-binding-for-authn-request.tooltip=Indikerer om AuthnRequests m\u00E5 bli sendt ved \u00E5 bruke en HTTP-POST binding. Hvis satt til false, vil HTTP-REDIRECT binding bli brukt. +want-authn-requests-signed=Vil ha AuthnRequests signert +want-authn-requests-signed.tooltip=Indikerer om identitetsleverand\u00F8r forventer en signert AuthnRequest. +force-authentication=Force autentisering +identity-provider.force-authentication.tooltip=Indikerer om identitetsleverand\u00F8r m\u00E5 autentisere presentat\u00F8ren direkte i stedet for \u00E5 stole p\u00E5 en tidligere sikkerhetskontekst. +validate-signature=Valider signatur +saml.validate-signature.tooltip=Aktiver/deaktiver signaturvalidering av SAML svar. +validating-x509-certificate=Validerer X509 sertifikat +validating-x509-certificate.tooltip=Sertifikatet i PEM format som m\u00E5 brukes for \u00E5 se etter signaturer. +saml.import-from-url.tooltip=Importer metadata fra et eksternt IDP SAML entity descriptor. +social.client-id.tooltip=Identifikator for klient registrert hos identitetsleverand\u00F8r. +social.client-secret.tooltip=Klient secret registrert hos identitetsleverand\u00F8r. +social.default-scopes.tooltip=Scopes som sendes n\u00E5r tillatelse for autorisasjon blir etterspurt. Se dokumentasjon for mulige verdier, separator og standardverdi. +key=N\u00F8kkel +stackoverflow.key.tooltip=N\u00F8kkelen er hentet fra klientregistrering p\u00E5 Stack Overflow. + +# User federation +sync-ldap-roles-to-keycloak=Synkroniser LDAP-roller til Keycloak +sync-keycloak-roles-to-ldap=Synkroniser Keycloak-roller til LDAP +sync-ldap-groups-to-keycloak=Synkroniser LDAP-grupper til Keycloak +sync-keycloak-groups-to-ldap=Synkroniser Keycloak-grupper til LDAP + +realms=Sikkerhetsdomener +realm=Sikkerhetsdomene + +identity-provider-mappers=Identitetsleverand\u00F8rmappere +create-identity-provider-mapper=Opprett identitetsleverand\u00F8rmappere +add-identity-provider-mapper=Legg til identitetsleverand\u00F8rmappere +client.description.tooltip=Angir beskrivelse av klienten. For eksempel\: 'Min klient for timelister'. St\u00F8tter n\u00F8kler for lokaliserte verdier. For eksempel\: ${my_client_description} + +expires=Utl\u00F8per +expiration=Holdbarhet +expiration.tooltip=Angir hvor lenge et token skal v\u00E6re gyldig +count=Teller +count.tooltip=Angir hvor mange klienter som kan bli opprettet ved \u00E5 bruke token. +remainingCount=Resterende antall +created=Opprettet +back=Tilbake +initial-access-tokens=F\u00F8rste access tokens +initial-access-tokens.tooltip=F\u00F8rste access tokens for dynamisk registrering av klienter. Foresp\u00F8rsler med disse tokens kan bli sent fra alle verter. +add-initial-access-tokens=Legg til f\u00F8rste access token +initial-access-token=F\u00F8rste access token +initial-access.copyPaste.tooltip=Kopier/lim inn f\u00F8rste access token f\u00F8r du navigerer vekk fra denne siden ettersom det ikke er mulig \u00E5 hente den senere. +continue=Fortsett +initial-access-token.confirm.title=Kopier f\u00F8rste access token +initial-access-token.confirm.text=Vennligst kopier og lim inn f\u00F8rste access token f\u00F8r du bekrefter ettersom den ikke kan hentes senere +no-initial-access-available=F\u00F8rste access token er ikke tilgjengelig + +trusted-hosts-legend=Betrodde verter for klientregistrering +trusted-hosts-legend.tooltip=Verter, som er betrodd for klientregistrering. klientregistreringsforesp\u00F8rsler fra disse vertene kan bli sent selv uten f\u00F8rste access token. Antall klientregistreringer fra denne spesifikke verten kan bli begrenset til et spesifisert antall. +no-client-trusted-hosts-available=Ingen betrodde verter er tilgjengelig +add-client-reg-trusted-host=Legg til betrodd vert +hostname=Vertsnavn +client-reg-hostname.tooltip=Vertsnavn eller IP-adresse. Klientregistreringsforesp\u00F8rsler fra denne verten/adressen vil bli betrodd og tillat til \u00E5 registrere en ny klient. +client-reg-count.tooltip=Tillat antall klientregistreringsforesp\u00F8rsler fra en spesifikk vert. Du m\u00E5 restarte denne n\u00E5r grensen er n\u00E5dd. +client-reg-remainingCount.tooltip=Gjenv\u00E6rende antall klientregistreringsforesp\u00F8rsler fra denne verten. Du m\u00E5 restarte denne n\u00E5r grensen er n\u00E5dd. +reset-remaining-count=Tilbakestill gjenst\u00E5ende antall + +client-templates=Klientmaler +client-templates.tooltip=Klientmaler tillater deg \u00E5 definere felles konfigurasjon som er delt av flere klienter. + +groups=Grupper + +group.add-selected.tooltip=Sikkerhetsdomene-roller som kan bli tildelt gruppen. +group.assigned-roles.tooltip=Sikkerhetsdomene-roller mappet til gruppen. +group.effective-roles.tooltip=Alle sikkerhetsdomene-rollemappinger. Noen roller kan ha blitt arvet fra en sammensatt rolle. +group.available-roles.tooltip=Roller som kan tildeles fra denne klienten. +group.assigned-roles-client.tooltip=Rollemapping for denne klienten. +group.effective-roles-client.tooltip=Rollemapping for denne klienten. Noen roller kan ha blitt arvet fra en mappet sammensatt rolle. + +default-roles=Standardroller +no-realm-roles-available=Ingen sikkerhetsdomener er tilgjengelig + +users=Brukere +user.add-selected.tooltip=Sikkerhetsdomene-roller som kan bli tildelt bruker. +user.assigned-roles.tooltip=Sikkerhetsdomene-roller mappet til bruker +user.effective-roles.tooltip=Alle sikkerhetsdomene-rollemappinger. Noen roller kan ha blitt arvet fra en sammensatt rolle. +user.available-roles.tooltip=Roller som kan tildeles fra denne klienten. +user.assigned-roles-client.tooltip=Rollemapping for denne klienten. +user.effective-roles-client.tooltip=Rollemapping for denne klienten. Noen roller kan ha blitt arvet fra en mappet sammensatt rolle. +default.available-roles.tooltip=Sikkerhetsdomene-roller som kan tildeles. +realm-default-roles=Standardroller for sikkerhetsdomene +realm-default-roles.tooltip=Sikkerhetsdomene-roller tildelt nye brukere. +default.available-roles-client.tooltip=Roller fra denne klienten som blir tildelt som standard. +client-default-roles=Standard klientroller +client-default-roles.tooltip=Roller fra denne klienten blir tildelt som standardrolle. +composite.available-roles.tooltip=Sikkerhetsdomene-roller knyttet til denne sammensatte rollen. +composite.associated-roles.tooltip=Sikkerhetsdomeneniv\u00E5-roller knyttet til denne sammensatte rollen. +composite.available-roles-client.tooltip=Roller fra denne klienten kan du assosiere med denne sammensatte rollen. +composite.associated-roles-client.tooltip=Klientroller knyttet til denne sammensatte rollen. +partial-import=Delvis import +partial-import.tooltip=Delvis import lar deg importere brukere, klienter og andre ressurser fra en tidligere eksportert json-fil. + +file=File +exported-json-file=Eksporter json-fil +import-from-realm=Importer fra sikkerhetsdomene +import-users=Importer brukere +import-groups=Importer grupper +import-clients=Importer klienter +import-identity-providers=Importer identitetsleverand\u00F8rer +import-realm-roles=Importer roller for sikkerhetsdomene +import-client-roles=Importer klientroller +if-resource-exists=Hvis en ressurs eksisterer +fail=Mislykkes +skip=Hopp over +overwrite=Skriv over +if-resource-exists.tooltip=Spesifiser hva som skal gj\u00F8res om du fors\u00F8ker \u00E5 importere en ressurs som allerede eksisterer. + +action=Handling +role-selector=Rollevelger +realm-roles.tooltip=Rolle for sikkerhetsdomene som kan velges. + +select-a-role=Velg en rolle +select-realm-role=Velg en rolle for sikkerhetsdomenet +client-roles.tooltip=Klientroller som kan velges. +select-client-role=Velg klientrolle + +client-template=Klientmal +client-template.tooltip=Klientmal som denne klienten arver konfigurasjonen fra +client-saml-endpoint=Endepunkt for klient-SAML +add-client-template=Legg til klientmal + +manage=H\u00E5ndter +authentication=Autentisering +user-storage=Brukerlagring +user-federation=Brukerfederering +events=Hendelser +realm-settings=Innstillinger for sikkerhetsdomene +configure=Konfigurer +select-realm=Velg sikkerhetsdomene +add=Legg til + +client-template.name.tooltip=Navn p\u00E5 klientmal. M\u00E5 v\u00E6re unik i sikkerhetsdomenet. +client-template.description.tooltip=Beskrivelse av klientmal +client-template.protocol.tooltip=Hvilken SSO protokoll-konfigurasjon som blir levert av denne klientmalen + +add-user-federation-provider=Legg til leverand\u00F8r for brukerfederering +add-user-storage-provider=Legg til brukerlagringsleverand\u00F8r +required-settings=P\u00E5krevde innstillinger +provider-id=Leverand\u00F8r-ID +console-display-name=Konsoll vis navn +console-display-name.tooltip=Viser navn p\u00E5 leverand\u00F8r n\u00E5r den er lenket i administratorkonsollen. +priority=Prioritet +priority.tooltip=Prioritet av leverand\u00F8r n\u00E5r et oppslag av bruker utf\u00F8res. Lavest f\u00F8rst. +sync-settings=Innstillinger for synkronisering +periodic-full-sync=Fullstendig periodisk synkronisering +periodic-full-sync.tooltip=Skal fullstendig periodisk synkronisering av leverand\u00F8r-brukere til Keycloak v\u00E6re aktivert eller deaktivert +full-sync-period=Fullstendig synkroniseringsperiode +full-sync-period.tooltip=Periode for fullstendig synkronisering i sekunder +periodic-changed-users-sync=Periodisk synkronisering av endrede brukere +periodic-changed-users-sync.tooltip=Skal periodisk synkronisering av endrede eller nylig opprettede leverand\u00F8r-brukere til Keycloak v\u00E6re aktivert eller deaktivert +changed-users-sync-period=Synkroniseringsperiode for endrede brukere +changed-users-sync-period.tooltip=Periode for synkronisering av endrede eller nylig opprettede leverand\u00F8r-brukere i sekunder. +synchronize-changed-users=Synkroniser endrede brukere +synchronize-all-users=Synkroniser alle brukere +kerberos-realm=Sikkerhetsdomene for Kerberos +kerberos-realm.tooltip=Navn p\u00E5 kerberos-sikkerhetsdomene. For eksempel FOO.ORG +server-principal=Server principal +server-principal.tooltip=Fullstendig navn p\u00E5 server principal for HTTP-service som inkluderer server og domenenavn. For eksempel HTTP/host.foo.org@FOO.ORG +keytab=KeyTab +keytab.tooltip=Plassering av Kerberos-keytab fil som inneholder legitimasjonsdetaljer for server principal. For eksempel /etc/krb5.keytab +debug=Feils\u00F8king +debug.tooltip=Aktiver/deaktiver logging av feils\u00F8king til standard output for Krb5LoginModule. +allow-password-authentication=Tillat autentisering med passord +allow-password-authentication.tooltip=Aktiver/deaktivert muligheten for autentisering med brukernavn/passord mot databasen til Kerberos +edit-mode=Redigeringsmodus +edit-mode.tooltip=READ_ONLY betyr at passordoppdateringer ikke er tillatt, og at bruker alltid autentiseres med et Kerberos-passord. UNSYNCED betyr at bruker kan endre sitt passord i databasen til Keycloak og at denne vil bli brukt i stedet for Kerberos-passordet. +ldap.edit-mode.tooltip=READ_ONLY er et skrivebeskyttet LDAP-lager. WRITABLE betyr at data vil bli synkronisert tilbake til LDAP p\u00E5 foresp\u00F8rsel. UNSYNCED betyr at brukerdata vil bli importert, men vil ikke bli synkronisert tilbake til LDAP. +update-profile-first-login=Oppdater profil ved f\u00F8rste innlogging +update-profile-first-login.tooltip=Oppdater profil ved f\u00F8rste innlogging +sync-registrations=Synkroniser registreringer +ldap.sync-registrations.tooltip=Skal nylig opprettede brukere bli opprettet innenfor et LDAP-lager? Prioritet p\u00E5virker hvilken leverand\u00F8r som er valgt for \u00E5 synkronisere den nye brukeren. +vendor=Leverand\u00F8r +ldap.vendor.tooltip=LDAP leverand\u00F8r (provider) +username-ldap-attribute=Brukernavn LDAP-attributt +ldap-attribute-name-for-username=LDAP-attributtnavn for brukernavn +username-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributt, som er kartlagt som et Keycloak-brukernavn. For mange LDAP-serverleverand\u00F8rer kan dette v\u00E6re 'uid'. For Active directory kan dette v\u00E6re 'sAMAccountName' eller 'cn'. Attributtet burde v\u00E6re fylt for alle LDAP-brukeroppf\u00F8ringer om du vil importere fra LDAP til Keycloak. +rdn-ldap-attribute=RDN LDAP-attributt +ldap-attribute-name-for-user-rdn=LDAP-attributtnavn for RDN-bruker +rdn-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributt, som brukes som RDN (topp-attributt) for en typisk DN-bruker. Vanligvis er dette det samme som LDAP-attributtet for brukernavn, men det er ikke p\u00E5krevd. For eksempel for Active directory er det vanlig \u00E5 bruke cn' som RDN-attributt n\u00E5r attributtet for brukernavn kan v\u00E6re 'sAMAccountName'. +uuid-ldap-attribute=UUID LDAP-attributt +ldap-attribute-name-for-uuid=LDAP-attributtnavn for UUID. +uuid-ldap-attribute.tooltip=Navn p\u00E5 LDAP-attributtet, som brukes som en unik objekt-identifikator (UUID) for objekter i LDAP. For mange LDAP-serverleverand\u00F8rer er det 'entryUUID', men noen er annerledes. For eksempel for Active directory b\u00F8r det v\u00E6re 'objectGUID'. Hvis din LDAP-server ikke st\u00F8tter UUID, kan du bruke hvilket som helst annet attributt, som er ment \u00E5 v\u00E6re unikt blant LDAP-brukere i treet. For eksempel 'uid' eller 'entryDN'. +user-object-classes=Brukerobjektklasser +ldap-user-object-classes.placeholder=Objektklasser for LDAP-bruker (separert med komma) + +ldap-connection-url=LDAP tilkoblings URL +ldap-users-dn=LDAP brukere DN +ldap-bind-dn=LDAP bind DN +ldap-bind-credentials=LDAP bind innloggingsdetaljer +ldap-filter=LDAP filter +ldap.user-object-classes.tooltip=Alle verdier av LDAP objectClass-attributtet for brukere i LDAP er separert med komma. For eksempel, 'inetOrgPerson, organizationalPerson'. Nylig opprettede Keycloak-brukere vil bli skrevet til LDAP med alle disse objektklassene og eksisterende LDAP-brukeroppf\u00F8ringer vil bli funnet om de inneholder de samme objektklassene. + +connection-url=Tilkoblings URL +ldap.connection-url.tooltip=Tilkoblings URL din til LDAP-server +test-connection=Testkobling +users-dn=DN-brukere +ldap.users-dn.tooltip=Fullstendig DN av LDAP-tre hvor dine brukere befinner seg. Denne spesifikke DN er forelder til LDAP-brukere. Den kan for eksempel v\u00E6re 'ou=users,dc=example,dc=com' hvis din typiske bruker vil ha en DN som 'uid=john,ou=users,dc=example,dc=com' +authentication-type=Autentiseringstype +ldap.authentication-type.tooltip=LDAP Autentiseringstype. For \u00F8yeblikket er kun mekanismene 'ingen' (anonym LDAP autentisering) eller 'enkel' (bind innloggingsdetaljer) tilgjengelig. +bind-dn=Bind DN +ldap.bind-dn.tooltip=DN av LDAP-administrator, som kan brukes av Keycloak for \u00E5 aksessere LDAP-server +bind-credential=Bind innloggingsdetaljer +ldap.bind-credential.tooltip=Passord for LDAP administrator +test-authentication=Testautentisering +custom-user-ldap-filter=Egendefinert filter for LDAP-bruker +ldap.custom-user-ldap-filter.tooltip=TilleggsLDAP-filter for \u00E5 filtrere s\u00F8kte brukere. La filteret v\u00E6re tomt om du ikke trenger et ekstra filter. Pass p\u00E5 at den starter med '(' og slutter med ')' +search-scope=Scope for s\u00F8k +ldap.search-scope.tooltip=For et niv\u00E5 s\u00F8ker vi etter brukere kun i DNser spesifisert av bruker-DNser. For subtre s\u00F8ker vi i hele subtreet. Se LDAP dokumentasjon for mer informasjon. +use-truststore-spi=Bruk Truststore SPI +ldap.use-truststore-spi.tooltip=Spesifiserer om LDAP-koblingen vil bruke truststore SPI med truststore konfigurert i keycloak-server.json. 'Alltid' betyr at den alltid vil brukes. 'Aldri' betyr at den ikke brukes. 'Kun for ldaps' betyr at den vil brukes hvis din koblings URL bruker ldaps. Merk at selv om keycloak-server.json ikke er konfigurert, vil default Java cacerts eller sertifikat spesifisert i 'javax.net.ssl.trustStore' bli brukt. +connection-pooling=Connection Pooling +ldap.connection-pooling.tooltip=Burde Keycloak bruke connection pooling for \u00E5 aksessere LDAP-serveren? +ldap.pagination.tooltip=St\u00F8tter LDAP-serveren paginering? +kerberos-integration=Kerberos Integrasjon +allow-kerberos-authentication=Tillat autentisering med Kerberos +ldap.allow-kerberos-authentication.tooltip=Aktiver/deaktiver HTTP autentisering av brukere med SPNEGO/Kerberos tokens. Informasjonen om autentiserte brukere vil bli klargjort fra denne LDAP-serveren. +use-kerberos-for-password-authentication=Bruk Kerberos for autentisering av passord +ldap.use-kerberos-for-password-authentication.tooltip=Bruk Kerberos-innloggingsmodul for \u00E5 autentisere brukernavn/passord mot Kerberos-server i stedet for autentisering mot LDAP-server med Directory Service API +batch-size=Batch st\u00F8rrelse +ldap.batch-size.tooltip=Antall LDAP-brukere som vil bli importert fra LDAP til Keycloak innen en enkelt transaksjon. +ldap.periodic-full-sync.tooltip=Om fullstendig periodisk synkronisering av LDAP-brukere til Keycloak vil v\u00E6re aktivert eller deaktivert. +ldap.periodic-changed-users-sync.tooltip=Om periodisk synkronisering av endret eller nylig opprettede LDAP-brukere til Keycloak vil v\u00E6re aktivert eller deaktivert. +ldap.changed-users-sync-period.tooltip=Tidsperiode for synkronisering av endrede eller nylig opprettede LDAP-brukere i sekunder. +user-federation-mappers=Mappere for brukerfederering +create-user-federation-mapper=Opprett mapper for brukerfederering +add-user-federation-mapper=Legg til mapper for brukerfederering +provider-name=Leverand\u00F8rnavn +no-user-federation-providers-configured=Ingen leverand\u00F8r for brukerfederering er konfigurert +no-user-storage-providers-configured=Ingen leverand\u00F8r for brukerlagring er konfigurert +add-identity-provider=Legg til identitetsleverand\u00F8r +add-identity-provider-link=Legg til lenke til identitetsleverand\u00F8r +identity-provider=Identitetsleverand\u00F8r +identity-provider-user-id=Bruker-ID for identitetsleverand\u00F8r +identity-provider-user-id.tooltip=Unik ID for brukeren p\u00E5 identitetsleverand\u00F8rens side +identity-provider-username=Brukernavn til identitetsleverand\u00F8r +identity-provider-username.tooltip=Brukernavn p\u00E5 identitetsleverand\u00F8rens side +pagination=Paginering + +browser-flow=Nettleserflyt +browser-flow.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for nettleser-autentisering. +registration-flow=Registreringsflyt +registration-flow.tooltip=Velg flyten du \u00F8nsker for registrering. +direct-grant-flow=Direct Grant Flyt +direct-grant-flow.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for direct grant autentisering. +reset-credentials=Tilbakestill innloggingsdetaljer +reset-credentials.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke n\u00E5r brukeren har glemt sine p\u00E5loggingsdetaljer. +client-authentication=Autentisering av klient +client-authentication.tooltip=Velg flyten du \u00F8nsker \u00E5 bruke for autentisering av klienter. +new=Ny +copy=Kopi +add-execution=Legg til eksekvering +add-flow=Legg til flyt +auth-type=Type auth +requirement=Krav +config=Konfig +no-executions-available=Ingen tilgjengelig eksekvering +authentication-flows=Autentiseringsflyt +create-authenticator-config=Opprett konfig for autentikator +authenticator.alias.tooltip=Navn p\u00E5 konfigurasjonen +otp-type=Type engangskode +time-based=Tidsbasert +counter-based=Tellerbasert +otp-type.tooltip=Totp er et tidsbasert engangspassord. 'hotp' er et teller basert engangspassord hvor serveren f\u00F8lger med p\u00E5 en teller som den kan hashe mot. +otp-hash-algorithm=OTP hash-algoritme +otp-hash-algorithm.tooltip=Hva slags hashing algoritme skal brukes for \u00E5 generere OTP. +number-of-digits=Antall siffer +otp.number-of-digits.tooltip=Hvor mange sifre skal OTP ha? +look-ahead-window=Look Ahead Window +otp.look-ahead-window.tooltip=Hvor langt frem b\u00F8r serveren se i tilfelle token generator og server er ute av tidssynkronisering eller tellersynkronisering? +initial-counter=Initiell teller +otp.initial-counter.tooltip=Hva b\u00F8r den initielle tellerverdien v\u00E6re? +otp-token-period=Engangskode token +otp-token-period.tooltip=Hvor mange sekunder burde et engangskode token v\u00E6re gyldig? Standard er satt til 30 sekunder. +table-of-password-policies=Liste over policy for passord +add-policy.placeholder=Legg til policy... +policy-type=Type policy +policy-value=Verdi for policy +admin-events=administratorhendelser +admin-events.tooltip=Viser lagrede administratorhendelser for sikkerhetsdomenet. Hendelser relaterer til administratorkontoen, for eksempel opprettelse av et sikkerhetsdomene. For \u00E5 aktivere persistente hendelser g\u00E5 til konfig. +login-events=innloggingshendelser +filter=Filtrer +update=Oppdater +reset=Tilbakestill +operation-types=Operasjonstyper +resource-types=Ressurstyper +select-operations.placeholder=Velg operasjoner... +select-resource-types.placeholder=Velg ressursyper... +resource-path=Filsti for ressurs +resource-path.tooltip=Sorter etter filsti for ressurs. St\u00F8tter jokertegn '*' for \u00E5 sjekke om den er lik en del av stien, og '**' for \u00E5 sjekke flere deler. For eksempel 'realms/*/clients/asbc' vil v\u00E6re lik klient-ID asbc i alle sikkerhetsdomener, mens 'realms/master/**' er lik alt i mastersikkerhetsdomenet. +date-(from)=Dato (Fra) +date-(to)=Dato (Til) +authentication-details=Autentiseringsdetaljer +ip-address=IP-adresse +time=Tid +operation-type=Operasjonstype +resource-type=Ressurstype +auth=Auth +representation=Representasjon +register=Registrer +required-action=P\u00E5krevd handling +default-action=Standard handling +auth.default-action.tooltip=Hvis aktivert, vil enhver ny bruker bli tilordnet denne p\u00E5krevde handlingen. +no-required-actions-configured=Ingen p\u00E5krevde handlinger er konfigurert +defaults-to-id=Standardverdi er id +flows=Flyt +bindings=Bindinger +required-actions=P\u00E5krevde handlinger +password-policy=Passordpolicy +otp-policy=Policy for engangskode +user-groups=Brukergruppe +default-groups=Standardgrupper +groups.default-groups.tooltip=Sett med grupper som nye brukere automatisk vil bli medlem av. +cut=Klipp +paste=Lim inn + +create-group=Opprett gruppe +create-authenticator-execution=Opprett autentiseringsutf\u00F8relse +create-form-action-execution=Opprett skjema for handlingsutf\u00F8relse +create-top-level-form=Opprett skjema for toppniv\u00E5 +flow.alias.tooltip=Spesifiserer visningsnavn for flyten. +top-level-flow-type=Flytstype for toppniv\u00E5 +flow.generic=generisk +flow.client=klient +top-level-flow-type.tooltip=Hvilken type toppniv\u00E5 flyt er det? Type 'klient' brukes for autentisering av klienter (applikasjoner) n\u00E5r generisk brukes for brukere og alt annet +create-execution-flow=Opprett eksekveringsflyt +flow-type=Type av flyt +flow.form.type=skjema +flow-type.tooltip=Hva slags skjema det er +form-provider=Skjemaleverand\u00F8r +default-groups.tooltip=Nyopprettede eller registrerte brukere vil automatisk bli lagt til disse gruppene +select-a-type.placeholder=velg en type +available-groups=Tilgjengelige grupper +available-groups.tooltip=Velg en gruppe du \u00F8nsker \u00E5 legge til som standard. +value=Verdi +table-of-group-members=Liste over gruppemedlemmer +last-name=Etternavn +first-name=Fornavn +email=E-postadresse +toggle-navigation=Toggle navigasjon +manage-account=Administrer konto +sign-out=Logg ut +server-info=Serverinformasjon +resource-not-found=Ressurs ikke funnet... +resource-not-found.instruction=Vi kunne ikke finne ressursen du leter etter. Vennligst kontroller at nettadressen du oppga er riktig. +go-to-the-home-page=G\u00E5 til hjemmeside » +page-not-found=Side ikke funnet... +page-not-found.instruction=Vi kunne ikke finne siden du ser etter. Vennligst kontroller at nettadressen du skrev inn er riktig. +events.tooltip=Viser lagrede hendelser for sikkerhetsdomenet. Hendelser er relatert til brukerkontoer, for eksempel innlogging av bruker. For \u00E5 aktivere persistente hendelser g\u00E5 til konfig. +select-event-types.placeholder=Velg hendelsestyper... +events-config.tooltip=Viser konfigurasjonsalternativer for \u00E5 muliggj\u00F8re persistente bruker- og administratorhendelser. +select-an-action.placeholder=Velg en handling... +event-listeners.tooltip=Konfigurer hvilke lyttere som skal motta eventer fra sikkerhetsdomenet. +login.save-events.tooltip=Hvis aktivert vil innloggingshendelser bli lagret i databasen, noe som gj\u00F8r hendelsene tilgjengelige for administrator og kontoadministrasjonskonsoll. +clear-events.tooltip=Sletter alle hendelser fra databasen. +events.expiration.tooltip=Setter utl\u00F8pstid for hendelser. Utl\u00F8pte hendelser vil med jevne mellomrom bli slettet fra databasen. +admin-events-settings=Innstillinger for administratorhendelser +save-events=Lagre hendelser +admin.save-events.tooltip=Hvis aktivert vil administratorhendelser bli lagret i databasen, som vil gj\u00F8re hendelsene tilgjengelige i administrasjonskonsollen. +saved-types.tooltip=Konfigurer hvilke eventtyper som lagres. +include-representation=Inkluder representasjon +include-representation.tooltip=Inkluder JSON-representasjon for \u00E5 skape og oppdatere foresp\u00F8rsler. +clear-admin-events.tooltip=Sletter alle administratorhendelser i databasen. +server-version=Serverversjon +info=Info +providers=Leverand\u00F8rer +server-time=Servertid +server-uptime=Oppetid for server +memory=Minne +total-memory=Totalt minne +free-memory=Ledig minne +used-memory=Brukt minne +system=System +current-working-directory=Gjeldende arbeidskatalog +java-version=Java versjon +java-vendor=Java leverand\u00F8r +java-runtime=Java Runtime +java-vm=Java VM +java-vm-version=Java VM versjon +java-home=Java hjem +user-name=Brukers navn +user-timezone=Tidssone for bruker +user-locale=Lokalitet for bruker +system-encoding=Systemenkoding +operating-system=Operativsystem (OS) +os-architecture=OS arkitektur +spi=SPI +granted-roles=Tildelte roller +granted-protocol-mappers=Innvilgede protokollmappere +additional-grants=Tillegsrettigheter +revoke=Opphev +new-password=Nytt passord +password-confirmation=Passord bekreftelse +reset-password=Tilbakestill passord +credentials.temporary.tooltip=Hvis aktivert, er brukeren p\u00E5krevd til \u00E5 endre passordet ved neste innlogging +remove-totp=Fjern TOTP +credentials.remove-totp.tooltip=Fjern generator for engangspassord for bruker. +reset-actions=Tilbakestill handlinger +credentials.reset-actions.tooltip=Sett med handlinger som kan utf\u00F8res ved \u00E5 sende en bruker en Tilbakestillingshandling for E-post. 'Verifiser e-post' sender en e-post til brukeren for \u00E5 verifisere e-postadresse. 'Oppdater profil' krever at bruker legger inn personlig informasjon. 'Oppdater passord' krever at bruker skriver inn et nytt passord. 'Konfigurer TOTP' krever installasjon av en passordgenerator for mobil. +reset-actions-email=Tilbakestillingshandling for E-post. +send-email=Send e-post +credentials.reset-actions-email.tooltip=Sender en e-post til en bruker med en lenke. Ved \u00E5 klikke p\u00E5 denne lenken vil brukeren f\u00E5 lov til \u00E5 utf\u00F8re tilbakestillingshandlinger. Brukeren trenger ikke logge inn f\u00F8r dette. For eksempel, sett handlingen for \u00E5 oppdatere passord, klikk p\u00E5 denne knappen, og brukeren vil kunne endre deres passord uten \u00E5 logge inn. +add-user=Legg til bruker +created-at=Opprettet ved +user-enabled=Bruker aktivert +user-enabled.tooltip=En deaktivert bruker kan ikke logge inn. +user-temporarily-locked=Bruker er midlertidig l\u00E5st. +user-temporarily-locked.tooltip=Brukeren kan ha blitt l\u00E5st p\u00E5 grunn av at innloggingsfors\u00F8k har feilet for mange ganger. +unlock-user=L\u00E5s opp bruker +federation-link=Federeringslenke +email-verified=E-post verifisert +email-verified.tooltip=Har brukerens e-post blitt verifisert? +required-user-actions=P\u00E5krevde brukerhandlinger +required-user-actions.tooltip=Krev en handling n\u00E5r brukeren logger inn. 'Verifiser e-post' sender en e-post til brukeren for \u00E5 verifisere deres e-postadresse. 'Oppdater profil' krever at bruker legger inn personlig informasjon. 'Oppdater passord' krever at bruker skriver inn et nytt passord. 'Konfigurer TOTP' krever installasjon av en passordgenerator for mobil. +locale=Lokalitet +select-one.placeholder=Velg en... +impersonate=Utgi deg for \u00E5 v\u00E6re bruker +impersonate-user=Utgi deg for \u00E5 v\u00E6re bruker +impersonate-user.tooltip=Logg inn som denne brukeren. Hvis bruker er i samme sikkerhetsdomene som deg, vil din n\u00E5v\u00E6rende innloggede sesjon bli logget ut f\u00F8r du blir logget inn som denne brukeren. +identity-provider-alias=Alias for identitetsleverand\u00F8r +provider-user-id=Bruker-ID for leverand\u00F8r +provider-username=Brukernavn for leverand\u00F8r +no-identity-provider-links-available=Ingen lenker for identitetsleverand\u00F8r er tilgjengelig +group-membership=Gruppemedlemskap +leave=Forlat +group-membership.tooltip=Gruppen som brukeren er medlem av. Velg en gruppe p\u00E5 listen og klikk p\u00E5 'Forlat' for \u00E5 forlate gruppen. +membership.available-groups.tooltip=Grupper som brukere kan bli medlem av. Velg en gruppe og klikk p\u00E5 'Bli med' knappen. +table-of-realm-users=Liste over sikkerhetsdomenebrukere +view-all-users=Se alle brukere +unlock-users=L\u00E5s opp brukere +no-users-available=Ingen brukere tilgjengelig +users.instruction=Vennligst skriv inn et s\u00F8k, eller klikk p\u00E5 Se alle brukere +consents=Samtykke +started=Startet +logout-all-sessions=Logg ut av alle sesjoner +logout=Logg ut +new-name=Nytt navn +ok=Ok +attributes=Attributter +role-mappings=Mapping av roller +members=Medlemmer +details=Detaljer +identity-provider-links=Lenker til identitetsleverand\u00F8r +register-required-action=Registrer p\u00E5krevd handling +gender=Kj\u00F8nn +address=Adresse +phone=Telefon +profile-url=Profil URL +picture-url=Bilde URL +website=Nettsted +import-keys-and-cert=Importer n\u00F8kler og sertifikat +import-keys-and-cert.tooltip=Last opp klientens n\u00F8kkelpar og sertifikat. +upload-keys=Last opp n\u00F8kler +download-keys-and-cert=Last ned n\u00F8kler og sertifikat +no-value-assigned.placeholder=Ingen tilordnet verdi +remove=Fjern +no-group-members=Ingen gruppemedlemmer +temporary=Midlertidig +join=Bli med +event-type=Hendelsestype +events-config=Hendelseskonfigurasjon +event-listeners=Hendelseslyttere +login-events-settings=Innstillinger for innloggingshendelser +clear-events=Fjern hendelser +saved-types=Lagrede typer +clear-admin-events=Fjern administratorhendelser +clear-changes=Fjern endringer +error=Feil + +# Authz + # Authz Common +authz-authorization=Autorisasjon +authz-owner=Eier +authz-uri=URI +authz-scopes=Scope +authz-resource=Ressurs +authz-resource-type=Ressurstype +authz-resources=Ressurser +authz-scope=Scope +authz-authz-scopes=Autorisasjonsscopes +authz-policies=Policier +authz-permissions=Tillatelser +authz-evaluate=Evaluer +authz-icon-uri=Ikon URI +authz-icon-uri.tooltip=En URI som peker til et ikon. +authz-select-scope=Velg et scope +authz-select-resource=Velg en ressurs +authz-associated-policies=Assosierte policier +authz-any-resource=Enhver ressurs +authz-any-scope=Ethvert scope +authz-any-role=Enhver rolle +authz-policy-evaluation=Evaluering av policy +authz-select-client=Velg en klient +authz-select-user=Velg en bruker +authz-entitlements=Rettigheter +authz-no-resources=Ingen ressurser +authz-result=Resultat +authz-authorization-services-enabled=Autorisasjon aktivert +authz-authorization-services-enabled.tooltip=Aktiver/deaktiver finkornet autorisasjonssupport for en klient +authz-required=P\u00E5krevd + +# Authz Settings +authz-import-config.tooltip=Importer en JSON-fil som inneholder innstillinger for autorisasjon for denne ressursserveren. + +authz-policy-enforcement-mode=Modus for h\u00E5ndhevelse av policy +authz-policy-enforcement-mode.tooltip=Modus for h\u00E5ndhevelse av policy dikterer hvordan policier blir h\u00E5ndhevet n\u00E5r autorisasjonsforesp\u00F8rsler blir evaluert. 'H\u00E5ndhevende' betyr at foresp\u00F8rsler blir nektet som standard selv om det ikke er en policy knyttet til en gitt ressurs. 'Ettergivende' betyr at foresp\u00F8rsler blir tillatt selv om det ikke er en policy knyttet til en gitt ressurs. 'Deaktivert' deaktiverer fullstendig evalueringen av policier og tillater tilgang til enhver ressurs. +authz-policy-enforcement-mode-enforcing=H\u00E5ndhevende +authz-policy-enforcement-mode-permissive=Ettergivende +authz-policy-enforcement-mode-disabled=Deaktivert + +authz-remote-resource-management=H\u00E5ndtering av ekstern ressurs +authz-remote-resource-management.tooltip=Skal ressursene bli h\u00E5ndtert eksternt av ressursserveren? Hvis satt til false kan ressursene kun bli h\u00E5ndtert fra denne administratorkonsollen. + +authz-export-settings=Eksporter innstillinger +authz-export-settings.tooltip=Eksporter og last ned alle innstillinger for autorisasjon for denne ressursserveren. + + # Authz Resource List +authz-no-resources-available=Ingen tilgjengelige ressurser. +authz-no-scopes-assigned=Ingen tildelte scopes. +authz-no-type-defined=Ingen definert type. +authz-no-permission-assigned=Ingen tillatelse er tildelt. +authz-no-policy-assigned=Ingen tildelt policy. +authz-create-permission=Opprett tillatelse + + # Authz Resource Detail +authz-add-resource=Legg til ressurs +authz-resource-name.tooltip=Et unikt navn for denne ressursen. Navnet kan bli brukt til \u00E5 identifisere en ressurs og er nyttig i sp\u00F8rringer for en bestemt ressurs. +authz-resource-owner.tooltip=Eieren av denne ressursen. +authz-resource-type.tooltip=Ressurstype. Den kan brukes til \u00E5 gruppere ulike ressursinstanser av samme type. +authz-resource-uri.tooltip=En URI som ogs\u00E5 kan brukes for \u00E5 identifisere denne ressursen. +authz-resource-scopes.tooltip=Scopes assosiert med denne ressursen. + + # Authz Scope List +authz-add-scope=Legg til scope +authz-no-scopes-available=Ingen tilgjengelige scopes. + + # Authz Scope Detail +authz-scope-name.tooltip=Et unikt navn for dette scopet. Navnet kan bli brukt for \u00E5 identifisere et scope, og er nyttig i sp\u00F8rringer for en bestemt ressurs. + + # Authz Policy List +authz-all-types=Alle typer +authz-create-policy=Opprett policy +authz-no-policies-available=Ingen tilgjengelige policier. + + # Authz Policy Detail +authz-policy-name.tooltip=Navnet p\u00E5 denne policien. +authz-policy-description.tooltip=En beskrivelse av denne policien. +authz-policy-logic=Logikk +authz-policy-logic-positive=Positiv +authz-policy-logic-negative=Negativ +authz-policy-logic.tooltip=Logikken som dikterer hvordan beslutningspolicien skal utf\u00F8rres. Hvis 'Positiv', vil resulterende effekt (tillate eller nekte) oppn\u00E5dd under evalueringen av denne policien bli brukt for \u00E5 ta en beslutning. Hvis 'Negativ', vil resulterende effekt bli opphevet, med andre ord blir en tillatelse til et avslag og motsatt. +authz-policy-apply-policy=Anvend policy +authz-policy-apply-policy.tooltip=Spesifiserer alle policies som m\u00E5 bli anvendt for scopes definert av denne policien eller tillatelsen. +authz-policy-decision-strategy=Beslutningsstrategi +authz-policy-decision-strategy.tooltip=Beslutningsstrategi som dikterer hvordan policies knyttet til en gitt policy blir evaluert og hvordan endelig avgj\u00F8relse oppn\u00E5s. 'Bekreftende' betyr at minst en policy m\u00E5 evalueres til en positiv beslutning for at den samlede avgj\u00F8relsen kan bli positiv. 'Enstemmig' betyr at alle policies m\u00E5 evalueres til en positiv beslutning for at den samlede avgj\u00F8relsen kan bli positiv. 'Konsensus' betyr at antall positive beslutninger m\u00E5 v\u00E6re h\u00F8yere enn antall negative beslutninger. Hvis antallet av positive og negative er likt, blir den samlede avgj\u00F8relsen negativ. +authz-policy-decision-strategy-affirmative=Bekreftende +authz-policy-decision-strategy-unanimous=Enstemmig +authz-policy-decision-strategy-consensus=Konsensus +authz-select-a-policy=Velg en policy + + # Authz Role Policy Detail +authz-add-role-policy=Legg til policy for rolle +authz-no-roles-assigned=Ingen tildelte roller. +authz-policy-role-roles.tooltip=Spesifiser sikkerhetsdomenerolle(r) som tillates av denne policien. +authz-policy-role-clients.tooltip=Velger en klient for \u00E5 filtrere klientroller som kan bli tatt i bruk av denne policien. +authz-policy-role-client-roles.tooltip=Spesifiserer klientroller tillatt av denne policien. + + # Authz User Policy Detail +authz-add-user-policy=Legg til policy for bruker +authz-no-users-assigned=Ingen tildelte brukere. +authz-policy-user-users.tooltip=Spesifiser bruker(e) som tillates av denne policien. + + # Authz Time Policy Detail +authz-add-time-policy=Legg til policy for tid +authz-policy-time-not-before.tooltip=Definerer tiden f\u00F8r policien M\u00C5 IKKE innvilges. Denne innvilges kun om gjeldende dato/tid er f\u00F8r eller lik denne verdien. +authz-policy-time-not-on-after=Ikke p\u00E5 eller etter +authz-policy-time-not-on-after.tooltip=Definerer tiden etter en policy M\u00C5 IKKE innvilges. Denne innvilges kun om gjeldende dato/tid er f\u00F8r eller lik denne verdien. + + # Authz Drools Policy Detail +authz-add-drools-policy=Legg til Drools policy +authz-policy-drools-maven-artifact-resolve=L\u00F8s +authz-policy-drools-maven-artifact=Policy for Maven artefakt. +authz-policy-drools-maven-artifact.tooltip=Et Maven GAV som peker til et artefakt hvor reglene vil bli lastet fra. Med en gang du har gitt GAV kan du klikke *L\u00F8s* for \u00E5 laste felter for b\u00E5de *Modul* og *Sesjon* +authz-policy-drools-module=Modul +authz-policy-drools-module.tooltip=Modulen som brukes av denne policien. Du m\u00E5 oppgi en modul for \u00E5 velge en bestemt \u00F8kt der reglene vil bli lastet fra. +authz-policy-drools-session=Sesjon +authz-policy-drools-session.tooltip=Sesjonen brukt av denne policien. Sesjonen vil gi alle regler for evaluering ved prosessering av policien. +authz-policy-drools-update-period=Oppdater periode +authz-policy-drools-update-period.tooltip=Spesifiserer et intervall for \u00E5 skanne etter oppdateringer for artefakter. + + # Authz JS Policy Detail +authz-add-js-policy=Legg til policy for JavaScript +authz-policy-js-code=Kode +authz-policy-js-code.tooltip=JavaScript-koden angir betingelsene for denne politikken. + + + # Authz Aggregated Policy Detail +authz-aggregated=Aggregert +authz-add-aggregated-policy=Legg til policy for aggregering. + + # Authz Permission List +authz-no-permissions-available=Ingen tilgjengelige tillatelser. + + # Authz Permission Detail +authz-permission-name.tooltip=Navnet p\u00E5 denne tillatelsen. +authz-permission-description.tooltip=En beskrivelse av denne tillatelsen. + + # Authz Resource Permission Detail +authz-add-resource-permission=Legg til tillatelse for ressurs. +authz-permission-resource-apply-to-resource-type=Bruk p\u00E5 ressurstype +authz-permission-resource-apply-to-resource-type.tooltip=Spesifiserer om denne tillatelsen skal gjelde for alle ressurser med en gitt type. I dette tilfellet vil tillatelsen bli evaluert for alle instanser av gitt ressurstype. +authz-permission-resource-resource.tooltip=Spesifiserer at denne tillatelsen m\u00E5 bli brukt for en spesifikk ressursinstans. +authz-permission-resource-type.tooltip=Spesifiserer at denne tillatelsen m\u00E5 bli anvendt for alle ressursinstanser for en gitt type. + + # Authz Scope Permission Detail +authz-add-scope-permission=Legg til tillatelse for scope +authz-permission-scope-resource.tooltip=Begrens scopes til de som er tilknyttet den valgte ressursen. Hvis dette ikke er valgt vil alle scopes v\u00E6re tilgjengelige. +authz-permission-scope-scope.tooltip=Spesifiserer at denne tillatelse m\u00E5 anvendes p\u00E5 en eller flere scopes. + + # Authz Evaluation +authz-evaluation-identity-information=Identitetsinformasjon +authz-evaluation-identity-information.tooltip=De tilgjengelige alternativene for \u00E5 konfigurere identitesinformasjon som vil bli brukt ved evaluering av policier. +authz-evaluation-client.tooltip=Velg klienten som vil utf\u00F8re denne autorisasjonsforesp\u00F8rselen. +authz-evaluation-user.tooltip=Velg en bruker hvis identitet vil bli brukt for \u00E5 s\u00F8ke tillatelser fra serveren. +authz-evaluation-role.tooltip=Velg en rolle som du vil knytte til den valgte brukeren. +authz-evaluation-new=Ny evaluering +authz-evaluation-re-evaluate=Re-evaluering +authz-evaluation-previous=Forrige evaluering +authz-evaluation-contextual-info=Kontekstuell informasjon +authz-evaluation-contextual-info.tooltip=Tilgjengelige valg for \u00E5 konfigurere enhver kontekstuell informasjon som vil bli brukt ved evaluering av policier. +authz-evaluation-contextual-attributes=Kontekstuelle attributter +authz-evaluation-contextual-attributes.tooltip=Ethvert attributt gitt av et kj\u00F8rende milj\u00F8 eller ved utf\u00F8relseskontekst. +authz-evaluation-permissions.tooltip=De tilgjengelige alternativene for \u00E5 konfigurere tillatelsene for hvilke policies som skal anvendes. +authz-evaluation-evaluate=Evaluer +authz-evaluation-any-resource-with-scopes=Enhver ressurs med scope(s) +authz-evaluation-no-result=Kunne ikke f\u00E5 et resultat for den gitte autorisasjonsforesp\u00F8rselen. Sjekk om de tilgjengelige ressursene er tilknyttet en policy. +authz-evaluation-no-policies-resource=Ingen policies ble funnet for denne ressursen. +authz-evaluation-result.tooltip=Det samlede resultatet for denne foresp\u00F8rselen for tillatelse. +authz-evaluation-scopes.tooltip=Liste over tillatte scopes. +authz-evaluation-policies.tooltip=Detaljer om hvilke policies som ble evaluert og deres avgj\u00F8relser. +authz-evaluation-authorization-data=Respons +authz-evaluation-authorization-data.tooltip=Representerer et token som b\u00E6rer autorisasjonsdata som et resultat av prosesseringen av en autorisasjonsforesp\u00F8rsel. Denne representasjonen er hva Keycloak sender ut til klienter som ettersp\u00F8r tillatelser. Sjekk autorisasjonsclaim for tillatelsene som ble gitt basert p\u00E5 n\u00E5v\u00E6rende autorisasjonsforesp\u00F8rsel. +authz-show-authorization-data=Vis autorisasjonsdata diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_no.properties b/themes/src/main/resources/theme/base/admin/messages/messages_no.properties new file mode 100644 index 0000000000..43cb61df08 --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/messages/messages_no.properties @@ -0,0 +1,14 @@ +invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}. +invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} sm\u00E5 bokstaver. +invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} sifre. +invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} store bokstaver. +invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minst {0} spesialtegn. +invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn. +invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster. +invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene. + +ldapErrorInvalidCustomFilter=Tilpasset konfigurasjon av LDAP-filter starter ikke med "(" eller slutter ikke med ")". +ldapErrorMissingClientId=KlientID m\u00E5 v\u00E6re tilgjengelig i config n\u00E5r sikkerhetsdomenerollemapping ikke brukes. +ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Ikke mulig \u00E5 bevare gruppearv og samtidig bruke UID medlemskapstype. +ldapErrorCantWriteOnlyForReadOnlyLdap=Kan ikke sette write-only n\u00E5r LDAP leverand\u00F8r-modus ikke er WRITABLE +ldapErrorCantWriteOnlyAndReadOnly=Kan ikke sette b\u00E5de write-only og read-only diff --git a/themes/src/main/resources/theme/base/email/messages/messages_no.properties b/themes/src/main/resources/theme/base/email/messages/messages_no.properties new file mode 100644 index 0000000000..32334e9d8d --- /dev/null +++ b/themes/src/main/resources/theme/base/email/messages/messages_no.properties @@ -0,0 +1,24 @@ +emailVerificationSubject=Bekreft e-postadresse +emailVerificationBody=Noen har opprettet en {2} konto med denne e-postadressen. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 bekrefte e-postadressen din\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke opprettet denne kontoen, vennligst ignorer denne meldingen. +emailVerificationBodyHtml=

    Noen har opprettet en {2} konto med denne e-postadressen. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 bekrefte e-postadressen din

    {0}

    Denne lenken vil utl\u00F8pe om {1} minutter.

    Hvis du ikke opprettet denne kontoen, vennligst ignorer denne meldingen.

    +identityProviderLinkSubject=Lenke {0} +identityProviderLinkBody=Noen vil koble din {1} konto med {0} konto til bruker {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 koble kontoene\n\n{3}\n\nDenne lenken vil utl\u00F8pe om {4} minutter\n\nHvis du ikke vil koble kontoene, vennligst ignorer denne meldingen. Hvis du kobler kontoene sammen vil du kunne logge inn til {1} gjennom {0}. +identityProviderLinkBodyHtml=

    Noen vil koble din {1} konto med {0} konto til bruker {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 koble kontoene.

    {3}

    Denne lenken vil utl\u00F8pe om {4} minutter.

    Hvis du ikke vil koble kontoene, vennligst ignorer denne meldingen. Hvis du kobler kontoene sammen vil du kunne logge inn til {1} gjennom {0}.

    +passwordResetSubject=Tilbakestill passord +passwordResetBody=Noen har bedt om \u00E5 endre innloggingsdetaljene til din konto {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 tilbakestille dem.\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke vil tilbakestille din innloggingsdata, vennligst ignorer denne meldingen og ingenting vil bli endret. +passwordResetBodyHtml=

    Noen har bedt om \u00E5 endre innloggingsdetaljene til din konto {2}. Hvis dette var deg, klikk p\u00E5 lenken nedenfor for \u00E5 tilbakestille dem.

    {0}

    Denne lenken vil utl\u00F8pe om {1} minutter.

    Hvis du ikke vil tilbakestille din innloggingsdata, vennligst ignorer denne meldingen og ingenting vil bli endret.

    +executeActionsSubject=Oppdater kontoen din +executeActionsBody=Administrator har anmodet at du oppdaterer din {2} konto. Klikk p\u00E5 lenken nedenfor for \u00E5 starte denne prosessen\n\n{0}\n\nDenne lenken vil utl\u00F8pe om {1} minutter.\n\nHvis du ikke var klar over at administrator har bedt om dette, vennligst ignorer denne meldingen og ingenting vil bli endret. +executeActionsBodyHtml=

    Administrator har anmodet at du oppdaterer din {2} konto. Klikk p\u00E5 linken nedenfor for \u00E5 starte denne prosessen.

    {0}

    Denne lenken vil utl\u00F8pe om {1} minutter.

    Hvis du ikke var klar over at administrator har bedt om dette, ignorer denne meldingen og ingenting vil bli endret.

    +eventLoginErrorSubject=Innlogging feilet +eventLoginErrorBody=Et mislykket innloggingsfors\u00F8k ble oppdaget p\u00E5 din konto p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. +eventLoginErrorBodyHtml=

    Et mislykket innloggingsfors\u00F8k ble oppdaget p\u00E5 din konto p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.

    +eventRemoveTotpSubject=Fjern engangskode +eventRemoveTotpBody=Engangskode ble fjernet fra kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. +eventRemoveTotpBodyHtml=

    Engangskode ble fjernet fra kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.

    +eventUpdatePasswordSubject=Oppdater passord +eventUpdatePasswordBody=Ditt passord ble endret i {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. +eventUpdatePasswordBodyHtml=

    Ditt passord ble endret i {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.

    +eventUpdateTotpSubject=Oppdater engangskode +eventUpdateTotpBody=Engangskode ble oppdatert for kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator. +eventUpdateTotpBodyHtml=

    Engangskode ble oppdatert for kontoen din p\u00E5 {0} fra {1}. Hvis dette ikke var deg, vennligst kontakt administrator.

    diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties index 4e1bd577b2..b07446bce0 100755 --- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -215,6 +215,7 @@ locale_es=Espa\u00F1ol locale_fr=Fran\u00e7ais locale_it=Italian locale_ja=\u65E5\u672C\u8A9E +locale_no=Norsk locale_pt_BR=Portugu\u00EAs (Brasil) locale_pt-BR=Portugu\u00EAs (Brasil) locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 @@ -226,4 +227,4 @@ clientDisabledMessage=Client disabled. invalidParameterMessage=Invalid parameter\: {0} alreadyLoggedIn=You are already logged in. -p3pPolicy=CP="This is not a P3P policy!" \ No newline at end of file +p3pPolicy=CP="This is not a P3P policy!" diff --git a/themes/src/main/resources/theme/base/login/messages/messages_no.properties b/themes/src/main/resources/theme/base/login/messages/messages_no.properties new file mode 100644 index 0000000000..a90af4627b --- /dev/null +++ b/themes/src/main/resources/theme/base/login/messages/messages_no.properties @@ -0,0 +1,229 @@ +doLogIn=Logg inn +doRegister=Registrer deg +doCancel=Avbryt +doSubmit=Send inn +doYes=Ja +doNo=Nei +doContinue=Fortsett +doAccept=Aksepter +doDecline=Avsl\u00E5 +doForgotPassword=Glemt passord? +doClickHere=Klikk her +doImpersonate=Utgi deg for \u00E5 v\u00E6re en annen bruker +kerberosNotConfigured=Kerberos er ikke konfigurert +kerberosNotConfiguredTitle=Kerberos er ikke konfigurert +bypassKerberosDetail=Enten er du ikke logget inn via Kerberos eller s\u00E5 st\u00F8tter ikke nettleseren innlogging med Kerberos. Vennligst klikk Fortsett for \u00E5 logge inn p\u00E5 andre m\u00E5ter +kerberosNotSetUp=Kerberos er ikke konfigurert. Du kan ikke logge inn. +registerWithTitle=Registrer deg med {0} +registerWithTitleHtml={0} +loginTitle=Logg inn p\u00E5 {0} +loginTitleHtml={0} +impersonateTitle={0} Gi deg ut for \u00E5 v\u00E6re en annen bruker +impersonateTitleHtml={0} Gi deg ut for \u00E5 v\u00E6re en annen bruker +realmChoice=Sikkerhetsdomene +unknownUser=Ukjent bruker +loginTotpTitle=Konfigurer autentifikator for mobil +loginProfileTitle=Oppdater konto +loginTimeout=Du brukte for lang tid p\u00E5 \u00E5 logge inn. Vennligst pr\u00F8v igjen. +oauthGrantTitle=Gi tilgang +oauthGrantTitleHtml={0} +errorTitle=Vi beklager... +errorTitleHtml=Vi beklager ... +emailVerifyTitle=E-postbekreftelse +emailForgotTitle=Glemt passord? +updatePasswordTitle=Oppdater passord +codeSuccessTitle=Suksesskode +codeErrorTitle=Feilkode\: {0} + +termsTitle=Vilk\u00E5r og betingelser +termsTitleHtml=Vilk\u00E5r og betingelser +termsText=

    Vilk\u00E5r og betingelser kommer

    + +recaptchaFailed=Ugyldig Bildebekreftelse +recaptchaNotConfigured=Bildebekreftelse er p\u00E5krevet, men er ikke konfigurert +consentDenied=Samtykke avsl\u00E5tt. + +noAccount=Ny bruker? +username=Brukernavn +usernameOrEmail=Brukernavn eller e-postadresse +firstName=Fornavn +givenName=Fornavn +fullName=Fullstendig navn +lastName=Etternavn +familyName=Etternavn +email=E-postadresse +password=Passord +passwordConfirm=Bekreft passord +passwordNew=Nytt passord +passwordNewConfirm=Bekreft nytt Passord +rememberMe=Husk meg +authenticatorCode=Engangskode +address=Adresse +street=Gate-/veinavn + husnummer +locality=By +region=Fylke +postal_code=Postnummer +country=Land +emailVerified=E-postadresse bekreftet +gssDelegationCredential=GSS legitimasjons-delegering + +loginTotpStep1=Installer FreeOTP eller Google Authenticator p\u00E5 din mobiltelefon. Begge applikasjoner er tilgjengelige p\u00E5 Google Play og Apple App Store. +loginTotpStep2=\u00C5pne applikasjonen og skann strekkoden eller skriv inn koden +loginTotpStep3=Skriv inn engangskoden fra applikasjonen og klikk send inn for \u00E5 fullf\u00F8re +loginTotpOneTime=Engangskode + +oauthGrantRequest=Vil du gi disse tilgangsrettighetene? +inResource=i + +emailVerifyInstruction1=En e-post med instruksjoner for \u00E5 bekrefte din e-postadresse har blitt sendt til deg. +emailVerifyInstruction2=Ikke mottatt en bekreftelseskode i e-posten vi sendte til deg? +emailVerifyInstruction3=for \u00E5 sende e-post p\u00E5 nytt. + +emailLinkIdpTitle=Lenke {0} +emailLinkIdp1=En e-post med instruksjoner for \u00E5 koble {0} konto med din {2} konto har blitt sendt til deg. +emailLinkIdp2=Ikke mottatt en bekreftelseskode i e-posten vi sendte til deg? +emailLinkIdp3=for \u00E5 sende e-post p\u00E5 nytt. + +backToLogin=« Tilbake til innlogging +emailInstruction=Skriv inn e-postadressen din og vi vil sende deg instruksjoner for hvordan du oppretter et nytt passord. + +copyCodeInstruction=Vennligst kopier denne koden og lim den inn i applikasjonen din: + +personalInfo=Personlig informasjon: +role_admin=Administrator +role_realm-admin=Administrator for sikkerhetsdomene +role_create-realm=Opprette sikkerhetsdomene +role_create-client=Opprette klient +role_view-realm=Se sikkerhetsdomene +role_view-users=Se brukere +role_view-applications=Se applikasjoner +role_view-clients=Se klienter +role_view-events=Se hendelser +role_view-identity-providers=Se identitetsleverand\u00F8rer +role_manage-realm=Administrere sikkerhetsdomene +role_manage-users=Administrere brukere +role_manage-applications=Administrere applikasjoner +role_manage-identity-providers=Administrere identitetsleverand\u00F8rer +role_manage-clients=Administrere klienter +role_manage-events=Administrere hendelser +role_view-profile=Se profil +role_manage-account=Administrere konto +role_read-token=Lese token +role_offline-access=Frakoblet tilgang +role_uma_authorization=Skaffe tillatelser +client_account=Konto +client_security-admin-console=Sikkerthetsadministrasjonskonsoll +client_realm-management=Sikkerhetsdomene-administrasjon +client_broker=Broker + +invalidUserMessage=Ugyldig brukernavn eller passord. +invalidEmailMessage=Ugyldig e-postadresse. +accountDisabledMessage=Konto er deaktivert, kontakt administrator. +accountTemporarilyDisabledMessage=Konto er midlertidig deaktivert, kontakt administrator eller pr\u00F8v p\u00E5 nytt senere. +expiredCodeMessage=Login ble tidsavbrutt. Vennligst logg inn p\u00E5 nytt. + +missingFirstNameMessage=Vennligst oppgi fornavn. +missingLastNameMessage=Vennligst oppgi etternavn. +missingEmailMessage=Vennligst oppgi e-postadresse. +missingUsernameMessage=Vennligst oppgi brukernavn. +missingPasswordMessage=Vennligst oppgi passord. +missingTotpMessage=Vennligst oppgi autentiseringskode. +notMatchPasswordMessage=Passordene er ikke like. + +invalidPasswordExistingMessage=Ugyldig eksisterende passord. +invalidPasswordConfirmMessage=Passord er ikke like. +invalidTotpMessage=Ugyldig engangskode. + +usernameExistsMessage=Brukernavnet finnes allerede. +emailExistsMessage=E-post finnes allerede. + +federatedIdentityExistsMessage=Bruker med {0} {1} finnes allerede. Vennligst logg inn p\u00E5 kontoadministratsjon for \u00E5 koble sammen kontoene. + +confirmLinkIdpTitle=Kontoen finnes allerede +federatedIdentityConfirmLinkMessage=Bruker med {0} {1} finnes allerede. Hvordan vil du fortsette? +federatedIdentityConfirmReauthenticateMessage=Bekreft at du er {0} for \u00E5 koble din konto med {1} +confirmLinkIdpReviewProfile=Se over og bekreft profil +confirmLinkIdpContinue=Legg til eksisterende konto + +configureTotpMessage=Du m\u00E5 sette opp en engangskode-generator for \u00E5 aktivere konto. +updateProfileMessage=Du m\u00E5 oppdatere brukerprofilen din for \u00E5 aktivere konto. +updatePasswordMessage=Du m\u00E5 skifte passord for \u00E5 aktivere kontoen din. +verifyEmailMessage=Du m\u00E5 bekrefte e-postadressen din for \u00E5 aktivere konto. +linkIdpMessage=You need to verify your email address to link your account with {0}. + +emailSentMessage=Du vil straks motta en e-post med ytterlige instruksjoner. +emailSendErrorMessage=Mislyktes \u00E5 sende e-post, vennligst pr\u00F8v igjen senere. + +accountUpdatedMessage=Din konto har blitt oppdatert. +accountPasswordUpdatedMessage=Ditt passord har blitt oppdatert. + +noAccessMessage=Ingen tilgang + +invalidPasswordMinLengthMessage=Ugyldig passord: minimum lengde {0}. +invalidPasswordMinDigitsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sifre. +invalidPasswordMinLowerCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} sm\u00E5 bokstaver. +invalidPasswordMinUpperCaseCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} store bokstaver. +invalidPasswordMinSpecialCharsMessage=Ugyldig passord: m\u00E5 inneholde minimum {0} spesialtegn. +invalidPasswordNotUsernameMessage=Ugyldig passord: kan ikke v\u00E6re likt brukernavn. +invalidPasswordRegexPatternMessage=Ugyldig passord: tilfredsstiller ikke kravene for passord-m\u00F8nster. +invalidPasswordHistoryMessage=Ugyldig passord: kan ikke v\u00E6re likt noen av de {0} foreg\u00E5ende passordene. + +failedToProcessResponseMessage=Kunne ikke behandle svar +httpsRequiredMessage=HTTPS p\u00E5krevd +realmNotEnabledMessage=Sikkerhetsdomene er ikke aktivert +invalidRequestMessage=Ugyldig foresp\u00F8rsel +failedLogout=Utlogging feilet +unknownLoginRequesterMessage=Ukjent anmoder for innlogging +loginRequesterNotEnabledMessage=Anmoder for innlogging er ikke aktivert +bearerOnlyMessage=Bearer-only applikasjoner har ikke lov til \u00E5 initiere innlogging via nettleser +standardFlowDisabledMessage=Klienten har ikke lov til \u00E5 initiere innlogging via nettleser med gitt response_type. Standard flow er deaktivert for denne klienten. +implicitFlowDisabledMessage=Klienten har ikke lov til \u00E5 initiere innlogging via nettleser med gitt response_type. Implicit flow er deaktivert for denne klienten. +invalidRedirectUriMessage=Ugyldig redirect uri +unsupportedNameIdFormatMessage=NameIDFormat er ikke st\u00F8ttet +invalidRequesterMessage=Ugyldig sender av foresp\u00F8rsel +registrationNotAllowedMessage=Registrering er ikke lov +resetCredentialNotAllowedMessage=Tilbakestilling av innloggingsdata er ikke lov + +permissionNotApprovedMessage=Tillatelse ikke godkjent. +noRelayStateInResponseMessage=Ingen relay state i svar fra identitetsleverand\u00F8r. +insufficientPermissionMessage=Utilstrekkelige rettigheter for \u00E5 koble identiteter. +couldNotProceedWithAuthenticationRequestMessage=Kunne ikke g\u00E5 videre med autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r. +couldNotObtainTokenMessage=Klarte ikke \u00E5 innhente token fra identitetsleverand\u00F8r. +unexpectedErrorRetrievingTokenMessage=Uventet feil ved henting av token fra identitetsleverand\u00F8r. +unexpectedErrorHandlingResponseMessage=Uventet feil ved h\u00E5ndtering av svar fra identitetsleverand\u00F8r. +identityProviderAuthenticationFailedMessage=Autentisering feilet. Kunne ikke autentisere med identitetsleverand\u00F8r. +identityProviderDifferentUserMessage= Autentisert som {0}, men forventet \u00E5 bli identifisert som {1} +couldNotSendAuthenticationRequestMessage=Kunne ikke sende autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r. +unexpectedErrorHandlingRequestMessage=Uventet feil ved h\u00E5ndtering av autentiseringsforesp\u00F8rsel til identitetsleverand\u00F8r. +invalidAccessCodeMessage=Ugyldig tilgangskode. +sessionNotActiveMessage=Sesjonen er ikke aktiv. +invalidCodeMessage=En feil oppstod, vennligst logg inn p\u00E5 nytt i din applikasjon. +identityProviderUnexpectedErrorMessage=Uventet feil ved autentisering med identitetsleverand\u00F8r +identityProviderNotFoundMessage=Kunne ikke finne en identitetsleverand\u00F8r med identifikatoren. +identityProviderLinkSuccess=Din konto ble suksessfullt koblet med {0} konto {1}. +staleCodeMessage=Denne siden er ikke lenger gyldig. Vennligst g\u00E5 tilbake til applikasjonen din og logg inn p\u00E5 nytt. +realmSupportsNoCredentialsMessage=Sikkerhetsdomene st\u00F8tter ingen legitimasjonstyper. +identityProviderNotUniqueMessage=Sikkerhetsdomene st\u00F8tter flere identitetsleverand\u00F8rer. Kunne ikke avgj\u00F8re hvilken identitetsleverand\u00F8r som burde brukes for autentisering. +emailVerifiedMessage=Din e-postadresse har blitt verifisert. +staleEmailVerificationLink=Lenken du klikket er utg\u00E5tt og er ikke lenger gyldig. Har du kanskje allerede bekreftet e-postadressen din? + +locale_ca=Catal\u00E0 +locale_de=Deutsch +locale_en=English +locale_es=Espa\u00F1ol +locale_fr=Fran\u00e7ais +locale_it=Italian +locale_ja=\u65E5\u672C\u8A9E +locale_no=Norsk +locale_pt_BR=Portugu\u00EAs (Brasil) +locale_pt-BR=Portugu\u00EAs (Brasil) +locale_ru=\u0420\u0443\u0441\u0441\u043A\u0438\u0439 + +backToApplication=« Tilbake til applikasjonen +missingParameterMessage=Manglende parameter\: {0} +clientNotFoundMessage=Klient ikke funnet. +clientDisabledMessage=Klient deaktivert. +invalidParameterMessage=Ugyldig parameter\: {0} +alreadyLoggedIn=Du er allerede innlogget. + +p3pPolicy=CP="Dette er ikke en P3P policy!" From a7f9a6e0952636310d2055b1f2496feef89a3dbc Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 29 Aug 2016 12:07:00 +0200 Subject: [PATCH 31/49] KEYCLOAK-3424 Support for import from public key --- .../ClientAttributeCertificateResource.java | 19 ++++++++- .../oauth/ClientAuthSignedJWTTest.java | 41 ++++++++++++++----- .../client-auth-test/certificate.pem | 34 +++++++++------ .../resources/client-auth-test/publickey.pem | 10 ++++- .../admin/resources/js/controllers/clients.js | 5 ++- 5 files changed, 80 insertions(+), 29 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java index f29ed8cf49..152d632941 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientAttributeCertificateResource.java @@ -66,6 +66,10 @@ import java.util.Map; */ public class ClientAttributeCertificateResource { + public static final String CERTIFICATE_PEM = "Certificate PEM"; + public static final String PUBLIC_KEY_PEM = "Public Key PEM"; + public static final String JSON_WEB_KEY_SET = "JSON Web Key Set"; + protected RealmModel realm; private RealmAuth auth; protected ClientModel client; @@ -195,12 +199,23 @@ public class ClientAttributeCertificateResource { Map> uploadForm = input.getFormDataMap(); String keystoreFormat = uploadForm.get("keystoreFormat").get(0).getBodyAsString(); List inputParts = uploadForm.get("file"); - if (keystoreFormat.equals("Certificate PEM")) { + if (keystoreFormat.equals(CERTIFICATE_PEM)) { String pem = StreamUtil.readString(inputParts.get(0).getBody(InputStream.class, null)); + + // Validate format + KeycloakModelUtils.getCertificate(pem); + info.setCertificate(pem); return info; + } else if (keystoreFormat.equals(PUBLIC_KEY_PEM)) { + String pem = StreamUtil.readString(inputParts.get(0).getBody(InputStream.class, null)); - } else if (keystoreFormat.equals("JSON Web Key Set (JWK)")) { + // Validate format + KeycloakModelUtils.getPublicKey(pem); + + info.setPublicKey(pem); + return info; + } else if (keystoreFormat.equals(JSON_WEB_KEY_SET)) { InputStream stream = inputParts.get(0).getBody(InputStream.class, null); JSONWebKeySet keySet = JsonSerialization.readValue(stream, JSONWebKeySet.class); PublicKey publicKey = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java index 087a1e651b..c1cbdea6ac 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java @@ -55,7 +55,9 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.services.util.CertificateInfoHelper; import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.auth.page.AuthRealm; @@ -70,6 +72,7 @@ import java.net.URL; import java.nio.file.Files; import java.security.KeyStore; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.*; @@ -81,7 +84,6 @@ import static org.junit.Assert.assertNotEquals; * @author Vaclav Muzikar */ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest { - public static final String CERTIFICATE_PEM = "Certificate PEM"; @Rule public AssertEvents events = new AssertEvents(this); @@ -349,8 +351,18 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest { } @Test - public void testUploadPEM() throws Exception { - testUploadKeystore(CERTIFICATE_PEM, "client-auth-test/certificate.pem", "undefined", "undefined"); + public void testUploadCertificatePEM() throws Exception { + testUploadKeystore(org.keycloak.services.resources.admin.ClientAttributeCertificateResource.CERTIFICATE_PEM, "client-auth-test/certificate.pem", "undefined", "undefined"); + } + + @Test + public void testUploadPublicKeyPEM() throws Exception { + testUploadKeystore(org.keycloak.services.resources.admin.ClientAttributeCertificateResource.PUBLIC_KEY_PEM, "client-auth-test/publickey.pem", "undefined", "undefined"); + } + + @Test + public void testUploadJWKS() throws Exception { + testUploadKeystore(org.keycloak.services.resources.admin.ClientAttributeCertificateResource.JSON_WEB_KEY_SET, "clientreg-test/jwks.json", "undefined", "undefined"); } // We need to test this as a genuine REST API HTTP request @@ -395,20 +407,27 @@ public class ClientAuthSignedJWTTest extends AbstractKeycloakTest { assertEquals(200, httpResponse.getStatusLine().getStatusCode()); client = getClient(testRealm.getRealm(), client.getId()).toRepresentation(); - String pem; // Assert the uploaded certificate - if (!keystoreFormat.equals(CERTIFICATE_PEM)) { + if (keystoreFormat.equals(org.keycloak.services.resources.admin.ClientAttributeCertificateResource.PUBLIC_KEY_PEM)) { + String pem = new String(Files.readAllBytes(keystoreFile.toPath())); + final String publicKeyNew = client.getAttributes().get(JWTClientAuthenticator.ATTR_PREFIX + "." + CertificateInfoHelper.PUBLIC_KEY); + assertEquals("Certificates don't match", pem, publicKeyNew); + } else if (keystoreFormat.equals(org.keycloak.services.resources.admin.ClientAttributeCertificateResource.JSON_WEB_KEY_SET)) { + final String publicKeyNew = client.getAttributes().get(JWTClientAuthenticator.ATTR_PREFIX + "." + CertificateInfoHelper.PUBLIC_KEY); + // Just assert it's valid public key + PublicKey pk = KeycloakModelUtils.getPublicKey(publicKeyNew); + Assert.assertNotNull(pk); + } else if (keystoreFormat.equals(org.keycloak.services.resources.admin.ClientAttributeCertificateResource.CERTIFICATE_PEM)) { + String pem = new String(Files.readAllBytes(keystoreFile.toPath())); + assertCertificate(client, certOld, pem); + } else { InputStream keystoreIs = new FileInputStream(keystoreFile); KeyStore keyStore = getKeystore(keystoreIs, storePassword, keystoreFormat); keystoreIs.close(); - pem = KeycloakModelUtils.getPemFromCertificate((X509Certificate) keyStore.getCertificate(keyAlias)); + String pem = KeycloakModelUtils.getPemFromCertificate((X509Certificate) keyStore.getCertificate(keyAlias)); + assertCertificate(client, certOld, pem); } - else { - pem = new String(Files.readAllBytes(keystoreFile.toPath())); - } - - assertCertificate(client, certOld, pem); } // TEST ERRORS diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/client-auth-test/certificate.pem b/testsuite/integration-arquillian/tests/base/src/test/resources/client-auth-test/certificate.pem index 4c589e81ed..a526c93da5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/client-auth-test/certificate.pem +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/client-auth-test/certificate.pem @@ -1,13 +1,21 @@ -MIICWwIBAAKBgQCqXOsp76WfwXSNCU5AOix6SDhNR7MOdEtawJRmdUcBuJd/QeFh -usLVOwm3a7s1134nlVKBEhOYBzKOY6LulvGDT4wHZUxE6cGJW+dEUd/6jrfZz9Ka -XyATzLUn2BKBQZqRleQPKupwaOGQqQGQ4OmJrWKqG77Rb5O7GLNNDRCGpwIDAQAB -AoGAA4sJLQcZ57erR9+gq/9Ju4VS/nB2Td+nTYzhyTu5LVT8eZ4SJOkwciTXL+Ri -sVz497PIUnzgwXGs4H87/UBQZ4UJNz+kTC3L6nr4b5bWoIxoiY76R+gzlYiNEV9s -QsX+K1FNKKMnEVa7t3DvbCFdlmt+4Agh/KQCU92Q13mj+WECQQDRF+BY1emtFfVC -2miuSWorMLoaKv5At3BMgcs4KPzlfpMiEOMM/m1jmGrqUm40RZlnkhKEqQB0VeqF -27gMGZuxAkEA0JTHqZDfcf5bBYhWzE3+Ec9A22/ZQgCiuV+DmWhCYxHLFsOYPkhT -0jAibfJc8E8VchXMjeDktBAyotylQO1V1wJAcZisLPdCdeOpFMH9/zopcP+PC+px -qY0/eIFHe+JgyvnCS86q3ANaQLKs21MfRkzNtAQbBXGfqpSGzmR5kDFVcQJAX1YR -qMSKDS2IZw/5NGrVnUhQybvm3s8xkW9B1GWeKnAglqgSNy2R+nl70ys7Ja/QCpRC -KftILbFVo7Eca3/PgwJAM5xMmg3Yx/WKkiCREvKXIGKOKN5rGWhHoLnYJHlsOi1r -Z7Bef90rMp2kDrfLUAgrIJH06kxB+3oPRcmgjWlmDQ== +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAIzE3vQp7EQWMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwMjI5MDgzMDU0WhcNNDMwNzE2MDgzMDU0WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAp1+GzdEkt2FZbISXYO12503FL6Oh8s4+tJ2fE66N8IezhugP8xiySDfW +TEMaO5Z2TaTnQQoF9SSZ9Edq1GPxpBX0cdkCOBopEGdlb3hUYDeMaDMs18KGemUc +Fj+CWB5VVcbmWMJ36WCz7FC+Oe38tmujR1AJpJL3pwqazyWIZzPqX8rW+rrNPGKP +C96oBPZMb4RJWivLBJi/o5MGSpo1sJNtxyF4zUUI00LX0wZAV1HH1XErd1Vz41on +nmB+tj9nevVRR4rDV280IELp9Ud0PIb3w843uJtwfSAwVG0pT6hv1VBDrBxTS08N +dPU8CtkQAXzCCr8nqfAbUFOhcWRQgQIDAQABo1AwTjAdBgNVHQ4EFgQUFE+uUZAI +n57ArEylqhCmHkAenTEwHwYDVR0jBBgwFoAUFE+uUZAIn57ArEylqhCmHkAenTEw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApkgD3OCtw3+sk7GR1YaJ +xNd8HT+fxXmnLqGnCQWX8lRIg5vj1PDMRev6vlIK3JfQV3zajcpKFfpy96klWsJy +ZLYBVW2QOtMzDdQ9I8dS4Pn/SJ/Vo/M/ucfY4ttcuUL3oQCrI/c/u9tcamGMfbwd +658MlXrUvt4B6qXY5AbgUvYR25P86uw7hSFMq5tQftNQsLbOh2FEeIiKhpgI7w8S +SPajaWjUXsfHc5H7f9MciE2NS1Vd3AViGrVWP1rgQ1Iv0UyQVQrnjmIs12ENJmTd +5lDqra5FJhaO7+RUG6er8n8HwXzhHkPmezGqtxWKikjitqvDY9prB3omJSa4Led+ +AQ== +-----END CERTIFICATE----- diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/client-auth-test/publickey.pem b/testsuite/integration-arquillian/tests/base/src/test/resources/client-auth-test/publickey.pem index 906ff55712..3169a04fc4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/client-auth-test/publickey.pem +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/client-auth-test/publickey.pem @@ -1 +1,9 @@ -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApnZ/E2BUULHjsRiSnEgZ +4vGe15BRqZPdkHR+NcvVYpThc7JqY6nZrdrwO9sOjlMC5e2Q18Fypi4KbJpGSe9r +0DPgcbPsHSoe2xFO3M8XBE0DyoRblaQFhe6p/sj3ak32k2zn+fMZUmlx/MTNQh1I +Cki7So0NDCBXt8XGZNnEyvKeXOUZP5qicP9KxVAQiWJvlkaTjc8rrRTmf+HWw/Qf +gQC0tzBRpa7T+RpW9O+rnWfOaNfTkTb9itIc+ZOa2Z4iidZ7+ifMOp9cNT641Wb6 +iYqJ2ufqY+msxI54tYM1tPgGS7r4SnCwmnqTaO383wXUl8TQ7qStmAWIepV3nNyu +AQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index d1041c7780..acbe6eca5c 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -365,11 +365,12 @@ module.controller('ClientCertificateImportCtrl', function($scope, $location, $ht ]; if (callingContext == 'jwt-credentials') { - $scope.keyFormats.push('JSON Web Key Set (JWK)'); + $scope.keyFormats.push('Public Key PEM'); + $scope.keyFormats.push('JSON Web Key Set'); } $scope.hideKeystoreSettings = function() { - return $scope.uploadKeyFormat == 'Certificate PEM' || $scope.uploadKeyFormat == 'JSON Web Key Set (JWK)'; + return $scope.uploadKeyFormat == 'Certificate PEM' || $scope.uploadKeyFormat == 'Public Key PEM' || $scope.uploadKeyFormat == 'JSON Web Key Set'; } $scope.uploadKeyFormat = $scope.keyFormats[0]; From f4aee129e4bd701d1a89de2278e85d8bfd48930f Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 29 Aug 2016 14:02:43 +0200 Subject: [PATCH 32/49] KEYCLOAK-3424 Issuer or token-endpoint as audience in signed JWT --- .../client/JWTClientAuthenticator.java | 11 +++++++---- .../client/OIDCClientRegistrationTest.java | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java index 5139e3a61d..789d38111c 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java @@ -30,6 +30,7 @@ import java.util.Set; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; import org.keycloak.OAuth2Constants; import org.keycloak.authentication.AuthenticationFlowError; @@ -42,6 +43,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.idm.CertificateRepresentation; @@ -139,10 +141,11 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator { throw new RuntimeException("Signature on JWT token failed validation"); } - // Validate other things - String expectedAudience = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName()); - if (!token.hasAudience(expectedAudience)) { - throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + Arrays.asList(token.getAudience()).toString() + "'"); + // Allow both "issuer" or "token-endpoint" as audience + String issuerUrl = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName()); + String tokenUrl = OIDCLoginProtocolService.tokenUrl(context.getUriInfo().getBaseUriBuilder()).build(realm.getName()).toString(); + if (!token.hasAudience(issuerUrl) && !token.hasAudience(tokenUrl)) { + throw new RuntimeException("Token audience doesn't match domain. Realm issuer is '" + issuerUrl + "' but audience from token is '" + Arrays.asList(token.getAudience()).toString() + "'"); } if (!token.isActive()) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index 5d7d2c14f0..1c6b85b80c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -37,8 +37,10 @@ import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation; @@ -53,6 +55,7 @@ import java.util.LinkedList; import java.util.List; import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriBuilder; import static org.junit.Assert.*; import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; @@ -260,7 +263,18 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { PrivateKey privateKey = KeycloakModelUtils.getPrivateKey(PRIVATE_KEY); - JWTClientCredentialsProvider jwtProvider = new JWTClientCredentialsProvider(); + // Use token-endpoint as audience as OIDC conformance testsuite is using it too. + JWTClientCredentialsProvider jwtProvider = new JWTClientCredentialsProvider() { + + @Override + protected JsonWebToken createRequestToken(String clientId, String realmInfoUrl) { + JsonWebToken jwt = super.createRequestToken(clientId, realmInfoUrl); + String tokenEndpointUrl = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(getAuthServerRoot())).build(REALM_NAME).toString(); + jwt.audience(tokenEndpointUrl); + return jwt; + } + + }; jwtProvider.setPrivateKey(privateKey); jwtProvider.setTokenTimeout(10); return jwtProvider.createSignedRequestToken(clientId, realmInfoUrl); From 02f28a7e8e355f25c8a74a1e11e862296112df6d Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 30 Aug 2016 20:20:44 +0200 Subject: [PATCH 33/49] KEYCLOAK-3416 Add support for signed Userinfo requests --- .../keycloak/representations/UserInfo.java | 62 ++++++++++++ .../java/org/keycloak/events/Details.java | 3 + .../oidc/OIDCAdvancedConfigWrapper.java | 94 +++++++++++++++++++ .../protocol/oidc/OIDCWellKnownProvider.java | 3 + .../oidc/endpoints/UserInfoEndpoint.java | 45 +++++++-- .../OIDCConfigurationRepresentation.java | 11 +++ .../oidc/DescriptionConverter.java | 15 +++ .../java/org/keycloak/utils/MediaType.java | 3 + .../testsuite/util/UserInfoClientUtil.java | 2 + .../client/OIDCClientRegistrationTest.java | 19 ++++ .../oidc/OIDCWellKnownProviderTest.java | 1 + .../keycloak/testsuite/oidc/UserInfoTest.java | 70 ++++++++++++++ .../messages/admin-messages_en.properties | 4 + .../admin/resources/js/controllers/clients.js | 26 +++++ .../resources/partials/client-detail.html | 17 ++++ 15 files changed, 367 insertions(+), 8 deletions(-) create mode 100644 services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java diff --git a/core/src/main/java/org/keycloak/representations/UserInfo.java b/core/src/main/java/org/keycloak/representations/UserInfo.java index 200e8e88d1..784971889f 100755 --- a/core/src/main/java/org/keycloak/representations/UserInfo.java +++ b/core/src/main/java/org/keycloak/representations/UserInfo.java @@ -16,12 +16,31 @@ */ package org.keycloak.representations; +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.keycloak.json.StringOrArrayDeserializer; +import org.keycloak.json.StringOrArraySerializer; /** * @author pedroigor */ public class UserInfo { + + // Should be in signed UserInfo response + @JsonProperty("iss") + protected String issuer; + @JsonProperty("aud") + @JsonSerialize(using = StringOrArraySerializer.class) + @JsonDeserialize(using = StringOrArrayDeserializer.class) + protected String[] audience; + @JsonProperty("sub") protected String sub; @@ -85,6 +104,34 @@ public class UserInfo { @JsonProperty("claims_locales") protected String claimsLocales; + protected Map otherClaims = new HashMap<>(); + + public String getIssuer() { + return issuer; + } + + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + @JsonIgnore + public String[] getAudience() { + return audience; + } + + public boolean hasAudience(String audience) { + for (String a : this.audience) { + if (a.equals(audience)) { + return true; + } + } + return false; + } + + public void setAudience(String... audience) { + this.audience = audience; + } + public String getSubject() { return this.sub; } @@ -260,4 +307,19 @@ public class UserInfo { public void setClaimsLocales(String claimsLocales) { this.claimsLocales = claimsLocales; } + + /** + * This is a map of any other claims and data that might be in the UserInfo. Could be custom claims set up by the auth server + * + * @return + */ + @JsonAnyGetter + public Map getOtherClaims() { + return otherClaims; + } + + @JsonAnySetter + public void setOtherClaims(String name, Object value) { + otherClaims.put(name, value); + } } diff --git a/server-spi/src/main/java/org/keycloak/events/Details.java b/server-spi/src/main/java/org/keycloak/events/Details.java index e5da713b69..772eaa794f 100755 --- a/server-spi/src/main/java/org/keycloak/events/Details.java +++ b/server-spi/src/main/java/org/keycloak/events/Details.java @@ -58,4 +58,7 @@ public interface Details { String CLIENT_AUTH_METHOD = "client_auth_method"; + String SIGNATURE_REQUIRED = "signature_required"; + String SIGNATURE_ALGORITHM = "signature_algorithm"; + } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java new file mode 100644 index 0000000000..37fe2043d6 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCAdvancedConfigWrapper.java @@ -0,0 +1,94 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.protocol.oidc; + +import java.util.HashMap; + +import org.keycloak.jose.jws.Algorithm; +import org.keycloak.models.ClientModel; +import org.keycloak.representations.idm.ClientRepresentation; + +/** + * @author Marek Posolda + */ +public class OIDCAdvancedConfigWrapper { + + private static final String USER_INFO_RESPONSE_SIGNATURE_ALG = "user.info.response.signature.alg"; + + private final ClientModel clientModel; + private final ClientRepresentation clientRep; + + private OIDCAdvancedConfigWrapper(ClientModel client, ClientRepresentation clientRep) { + this.clientModel = client; + this.clientRep = clientRep; + } + + + public static OIDCAdvancedConfigWrapper fromClientModel(ClientModel client) { + return new OIDCAdvancedConfigWrapper(client, null); + } + + public static OIDCAdvancedConfigWrapper fromClientRepresentation(ClientRepresentation clientRep) { + return new OIDCAdvancedConfigWrapper(null, clientRep); + } + + + public Algorithm getUserInfoSignedResponseAlg() { + String alg = getAttribute(USER_INFO_RESPONSE_SIGNATURE_ALG); + return alg==null ? null : Enum.valueOf(Algorithm.class, alg); + } + + public void setUserInfoSignedResponseAlg(Algorithm alg) { + String algStr = alg==null ? null : alg.toString(); + setAttribute(USER_INFO_RESPONSE_SIGNATURE_ALG, algStr); + } + + public boolean isUserInfoSignatureRequired() { + return getUserInfoSignedResponseAlg() != null; + } + + + private String getAttribute(String attrKey) { + if (clientModel != null) { + return clientModel.getAttribute(attrKey); + } else { + return clientRep.getAttributes()==null ? null : clientRep.getAttributes().get(attrKey); + } + } + + private void setAttribute(String attrKey, String attrValue) { + if (clientModel != null) { + if (attrValue != null) { + clientModel.setAttribute(attrKey, attrValue); + } else { + clientModel.removeAttribute(attrKey); + } + } else { + if (attrValue != null) { + if (clientRep.getAttributes() == null) { + clientRep.setAttributes(new HashMap<>()); + } + clientRep.getAttributes().put(attrKey, attrValue); + } else { + if (clientRep.getAttributes() != null) { + clientRep.getAttributes().put(attrKey, null); + } + } + } + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java index eb5b5b411e..dfca14434f 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java @@ -48,6 +48,8 @@ public class OIDCWellKnownProvider implements WellKnownProvider { public static final List DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.RS256.toString()); + public static final List DEFAULT_USER_INFO_SIGNING_ALG_VALUES_SUPPORTED = list(Algorithm.RS256.toString()); + public static final List DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS); public static final List DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, OIDCResponseType.TOKEN, "id_token token", "code id_token", "code token", "code id_token token"); @@ -90,6 +92,7 @@ public class OIDCWellKnownProvider implements WellKnownProvider { config.setRegistrationEndpoint(RealmsResource.clientRegistrationUrl(uriInfo).path(ClientRegistrationService.class, "provider").build(realm.getName(), OIDCClientRegistrationProviderFactory.ID).toString()); config.setIdTokenSigningAlgValuesSupported(DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); + config.setUserInfoSigningAlgValuesSupported(DEFAULT_USER_INFO_SIGNING_ALG_VALUES_SUPPORTED); config.setResponseTypesSupported(DEFAULT_RESPONSE_TYPES_SUPPORTED); config.setSubjectTypesSupported(DEFAULT_SUBJECT_TYPES_SUPPORTED); config.setResponseModesSupported(DEFAULT_RESPONSE_MODES_SUPPORTED); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java index ad23283d14..f5e4caab17 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/UserInfoEndpoint.java @@ -19,6 +19,7 @@ package org.keycloak.protocol.oidc.endpoints; import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpResponse; +import org.keycloak.OAuth2Constants; import org.keycloak.common.ClientConnection; import org.keycloak.OAuthErrorException; import org.keycloak.RSATokenVerifier; @@ -27,12 +28,15 @@ import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; +import org.keycloak.jose.jws.Algorithm; +import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.representations.AccessToken; import org.keycloak.services.ErrorResponseException; @@ -40,17 +44,18 @@ import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.resources.Cors; import org.keycloak.services.Urls; +import org.keycloak.utils.MediaType; import javax.ws.rs.GET; import javax.ws.rs.OPTIONS; import javax.ws.rs.POST; import javax.ws.rs.Path; -import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; + +import java.security.PrivateKey; import java.util.HashMap; import java.util.Map; @@ -86,7 +91,6 @@ public class UserInfoEndpoint { @Path("/") @OPTIONS - @Produces(MediaType.APPLICATION_JSON) public Response issueUserInfoPreflight() { return Cors.add(this.request, Response.ok()).auth().preflight().build(); } @@ -94,7 +98,6 @@ public class UserInfoEndpoint { @Path("/") @GET @NoCache - @Produces(MediaType.APPLICATION_JSON) public Response issueUserInfoGet(@Context final HttpHeaders headers) { String accessToken = this.appAuthManager.extractAuthorizationHeaderToken(headers); return issueUserInfo(accessToken); @@ -103,7 +106,6 @@ public class UserInfoEndpoint { @Path("/") @POST @NoCache - @Produces(MediaType.APPLICATION_JSON) public Response issueUserInfoPost() { // Try header first HttpHeaders headers = request.getHttpHeaders(); @@ -176,12 +178,39 @@ public class UserInfoEndpoint { AccessToken userInfo = new AccessToken(); tokenManager.transformUserInfoAccessToken(session, userInfo, realm, clientModel, userModel, userSession, clientSession); - event.success(); - Map claims = new HashMap(); claims.putAll(userInfo.getOtherClaims()); claims.put("sub", userModel.getId()); - return Cors.add(request, Response.ok(claims)).auth().allowedOrigins(token).build(); + + Response.ResponseBuilder responseBuilder; + OIDCAdvancedConfigWrapper cfg = OIDCAdvancedConfigWrapper.fromClientModel(clientModel); + + if (cfg.isUserInfoSignatureRequired()) { + String issuerUrl = Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()); + String audience = clientModel.getClientId(); + claims.put("iss", issuerUrl); + claims.put("aud", audience); + + Algorithm signatureAlg = cfg.getUserInfoSignedResponseAlg(); + PrivateKey privateKey = realm.getPrivateKey(); + + String signedUserInfo = new JWSBuilder() + .jsonContent(claims) + .sign(signatureAlg, privateKey); + + responseBuilder = Response.ok(signedUserInfo).header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JWT); + + event.detail(Details.SIGNATURE_REQUIRED, "true"); + event.detail(Details.SIGNATURE_ALGORITHM, cfg.getUserInfoSignedResponseAlg().toString()); + } else { + responseBuilder = Response.ok(claims).header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); + + event.detail(Details.SIGNATURE_REQUIRED, "false"); + } + + event.success(); + + return Cors.add(request, responseBuilder).auth().allowedOrigins(token).build(); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java index 0421e16edd..ee5241baa0 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCConfigurationRepresentation.java @@ -64,6 +64,9 @@ public class OIDCConfigurationRepresentation { @JsonProperty("id_token_signing_alg_values_supported") private List idTokenSigningAlgValuesSupported; + @JsonProperty("userinfo_signing_alg_values_supported") + private List userInfoSigningAlgValuesSupported; + @JsonProperty("response_modes_supported") private List responseModesSupported; @@ -184,6 +187,14 @@ public class OIDCConfigurationRepresentation { this.idTokenSigningAlgValuesSupported = idTokenSigningAlgValuesSupported; } + public List getUserInfoSigningAlgValuesSupported() { + return userInfoSigningAlgValuesSupported; + } + + public void setUserInfoSigningAlgValuesSupported(List userInfoSigningAlgValuesSupported) { + this.userInfoSigningAlgValuesSupported = userInfoSigningAlgValuesSupported; + } + public List getResponseModesSupported() { return responseModesSupported; } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java index 3ddcc59a74..a95cc090d2 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java @@ -24,8 +24,10 @@ import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthen import org.keycloak.authentication.authenticators.client.JWTClientAuthenticator; import org.keycloak.jose.jwk.JSONWebKeySet; import org.keycloak.jose.jwk.JWK; +import org.keycloak.jose.jws.Algorithm; import org.keycloak.models.KeycloakSession; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil; import org.keycloak.protocol.oidc.utils.JWKSUtils; @@ -102,6 +104,13 @@ public class DescriptionConverter { CertificateInfoHelper.updateClientRepresentationCertificateInfo(client, rep, JWTClientAuthenticator.ATTR_PREFIX); } + if (clientOIDC.getUserinfoSignedResponseAlg() != null) { + String userInfoSignedResponseAlg = clientOIDC.getUserinfoSignedResponseAlg(); + Algorithm algorithm = Enum.valueOf(Algorithm.class, userInfoSignedResponseAlg); + + OIDCAdvancedConfigWrapper.fromClientRepresentation(client).setUserInfoSignedResponseAlg(algorithm); + } + return client; } @@ -152,6 +161,12 @@ public class DescriptionConverter { response.setRegistrationClientUri(uri.toString()); response.setResponseTypes(getOIDCResponseTypes(client)); response.setGrantTypes(getOIDCGrantTypes(client)); + + OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(client); + if (config.isUserInfoSignatureRequired()) { + response.setUserinfoSignedResponseAlg(config.getUserInfoSignedResponseAlg().toString()); + } + return response; } diff --git a/services/src/main/java/org/keycloak/utils/MediaType.java b/services/src/main/java/org/keycloak/utils/MediaType.java index 31ab972392..c34858dccd 100644 --- a/services/src/main/java/org/keycloak/utils/MediaType.java +++ b/services/src/main/java/org/keycloak/utils/MediaType.java @@ -31,4 +31,7 @@ public class MediaType { public static final String APPLICATION_FORM_URLENCODED = javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED; public static final javax.ws.rs.core.MediaType APPLICATION_FORM_URLENCODED_TYPE = javax.ws.rs.core.MediaType.APPLICATION_FORM_URLENCODED_TYPE; + public static final String APPLICATION_JWT = "application/jwt"; + public static final javax.ws.rs.core.MediaType APPLICATION_JWT_TYPE = new javax.ws.rs.core.MediaType("application", "jwt"); + } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java index dd6a3db2c9..b5af7c9012 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/UserInfoClientUtil.java @@ -28,6 +28,7 @@ import javax.ws.rs.core.UriBuilder; import org.junit.Assert; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.UserInfo; +import org.keycloak.utils.MediaType; /** * @author Marek Posolda @@ -51,6 +52,7 @@ public class UserInfoClientUtil { public static void testSuccessfulUserInfoResponse(Response response, String expectedUsername, String expectedEmail) { Assert.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JSON); UserInfo userInfo = response.readEntity(UserInfo.class); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index 1c6b85b80c..9c78c4b040 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -35,7 +35,9 @@ import org.keycloak.common.util.CollectionUtil; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.constants.ServiceUrlConstants; import org.keycloak.jose.jwk.JSONWebKeySet; +import org.keycloak.jose.jws.Algorithm; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.utils.OIDCResponseType; @@ -44,6 +46,7 @@ import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation; import org.keycloak.representations.idm.ClientInitialAccessPresentation; import org.keycloak.representations.idm.ClientRegistrationTrustedHostRepresentation; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.oidc.OIDCClientRepresentation; import org.keycloak.testsuite.Assert; @@ -155,6 +158,7 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { assertEquals(Arrays.asList("code", "none"), response.getResponseTypes()); assertEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN), response.getGrantTypes()); assertEquals(OIDCLoginProtocol.CLIENT_SECRET_BASIC, response.getTokenEndpointAuthMethod()); + Assert.assertNull(response.getUserinfoSignedResponseAlg()); } @Test @@ -255,6 +259,21 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { Assert.assertEquals(response.getClientId(), accessToken.getAudience()[0]); } + @Test + public void testSignaturesRequired() throws Exception { + OIDCClientRepresentation clientRep = createRep(); + clientRep.setUserinfoSignedResponseAlg(Algorithm.RS256.toString()); + + OIDCClientRepresentation response = reg.oidc().create(clientRep); + Assert.assertEquals(Algorithm.RS256.toString(), response.getUserinfoSignedResponseAlg()); + Assert.assertNotNull(response.getClientSecret()); + + // Test Keycloak representation + ClientRepresentation kcClient = getClient(response.getClientId()); + OIDCAdvancedConfigWrapper config = OIDCAdvancedConfigWrapper.fromClientRepresentation(kcClient); + Assert.assertEquals(config.getUserInfoSignedResponseAlg(), Algorithm.RS256); + } + // Client auth with signedJWT - helper methods diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java index ab5c2304be..91ccc7e691 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCWellKnownProviderTest.java @@ -86,6 +86,7 @@ public class OIDCWellKnownProviderTest extends AbstractKeycloakTest { Assert.assertNames(oidcConfig.getSubjectTypesSupported(), "public"); Assert.assertNames(oidcConfig.getIdTokenSigningAlgValuesSupported(), Algorithm.RS256.toString()); + Assert.assertNames(oidcConfig.getUserInfoSigningAlgValuesSupported(), Algorithm.RS256.toString()); // Client authentication Assert.assertNames(oidcConfig.getTokenEndpointAuthMethodsSupported(), "client_secret_basic", "client_secret_post", "private_key_jwt"); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java index 268333f0e5..e0588e79e5 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java @@ -21,19 +21,31 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.keycloak.OAuth2Constants; +import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventType; +import org.keycloak.jose.jws.Algorithm; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.crypto.RSAProvider; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.UserInfo; +import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.Urls; import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.util.ClientManager; import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.UserInfoClientUtil; import org.keycloak.util.BasicAuthHelper; +import org.keycloak.util.JsonSerialization; +import org.keycloak.utils.MediaType; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; @@ -45,6 +57,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; import java.net.URI; +import java.security.PublicKey; import java.util.List; import static org.junit.Assert.assertEquals; @@ -152,6 +165,62 @@ public class UserInfoTest extends AbstractKeycloakTest { } } + @Test + public void testSuccessSignedResponse() throws Exception { + // Require signed userInfo request + ClientResource clientResource = ApiUtil.findClientByClientId(adminClient.realm("test"), "test-app"); + ClientRepresentation clientRep = clientResource.toRepresentation(); + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUserInfoSignedResponseAlg(Algorithm.RS256); + clientResource.update(clientRep); + + // test signed response + Client client = ClientBuilder.newClient(); + + try { + AccessTokenResponse accessTokenResponse = executeGrantAccessTokenRequest(client); + + Response response = UserInfoClientUtil.executeUserInfoRequest_getMethod(client, accessTokenResponse.getToken()); + + events.expect(EventType.USER_INFO_REQUEST) + .session(Matchers.notNullValue(String.class)) + .detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN) + .detail(Details.USERNAME, "test-user@localhost") + .detail(Details.SIGNATURE_REQUIRED, "true") + .detail(Details.SIGNATURE_ALGORITHM, Algorithm.RS256.toString()) + .assertEvent(); + + // Check signature and content + RealmRepresentation realmRep = adminClient.realm("test").toRepresentation(); + PublicKey publicKey = KeycloakModelUtils.getPublicKey(realmRep.getPublicKey()); + + Assert.assertEquals(200, response.getStatus()); + Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JWT); + String signedResponse = response.readEntity(String.class); + response.close(); + + JWSInput jwsInput = new JWSInput(signedResponse); + Assert.assertTrue(RSAProvider.verify(jwsInput, publicKey)); + + UserInfo userInfo = JsonSerialization.readValue(jwsInput.getContent(), UserInfo.class); + + Assert.assertNotNull(userInfo); + Assert.assertNotNull(userInfo.getSubject()); + Assert.assertEquals("test-user@localhost", userInfo.getEmail()); + Assert.assertEquals("test-user@localhost", userInfo.getPreferredUsername()); + + Assert.assertTrue(userInfo.hasAudience("test-app")); + String expectedIssuer = Urls.realmIssuer(new URI(AUTH_SERVER_ROOT), "test"); + Assert.assertEquals(expectedIssuer, userInfo.getIssuer()); + + } finally { + client.close(); + } + + // Revert signed userInfo request + OIDCAdvancedConfigWrapper.fromClientRepresentation(clientRep).setUserInfoSignedResponseAlg(null); + clientResource.update(clientRep); + } + @Test public void testSessionExpired() throws Exception { Client client = ClientBuilder.newClient(); @@ -235,6 +304,7 @@ public class UserInfoTest extends AbstractKeycloakTest { .session(Matchers.notNullValue(String.class)) .detail(Details.AUTH_METHOD, Details.VALIDATE_ACCESS_TOKEN) .detail(Details.USERNAME, "test-user@localhost") + .detail(Details.SIGNATURE_REQUIRED, "false") .assertEvent(); UserInfoClientUtil.testSuccessfulUserInfoResponse(response, "test-user@localhost", "test-user@localhost"); } diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index a753ae9175..b1b08debf5 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -236,6 +236,10 @@ idp-sso-relay-state=IDP Initiated SSO Relay State idp-sso-relay-state.tooltip=Relay state you want to send with SAML request when you want to do IDP Initiated SSO. web-origins=Web Origins web-origins.tooltip=Allowed CORS origins. To permit all origins of Valid Redirect URIs add '+'. To permit all origins add '*'. +fine-oidc-endpoint-conf=Fine Grain OpenID Connect Configuration +fine-oidc-endpoint-conf.tooltip=Expand this section to configure advanced settings of this client related to OpenID Connect protocol +user-info-signed-response-alg=User Info Signed Response Algorithm +user-info-signed-response-alg.tooltip=JWA algorithm used for signed User Info Endpoint response. If set to 'unsigned', then User Info Response won't be signed and will be returned in application/json format. fine-saml-endpoint-conf=Fine Grain SAML Endpoint Configuration fine-saml-endpoint-conf.tooltip=Expand this section to configure exact URLs for Assertion Consumer and Single Logout Service. assertion-consumer-post-binding-url=Assertion Consumer Service POST Binding URL diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index acbe6eca5c..3f13573653 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -792,6 +792,11 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, {name: "INCLUSIVE_WITH_COMMENTS", value: "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"} ]; + $scope.oidcSignatureAlgorithms = [ + "unsigned", + "RS256" + ]; + $scope.realm = realm; $scope.samlAuthnStatement = false; $scope.samlMultiValuedRoles = false; @@ -892,6 +897,8 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $scope.samlForcePostBinding = false; } } + + $scope.userInfoSignedResponseAlg = getSignatureAlgorithm('user.info.response'); } if (!$scope.create) { @@ -956,6 +963,25 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, $scope.client.attributes['saml_name_id_format'] = $scope.nameIdFormat; }; + $scope.changeUserInfoSignedResponseAlg = function() { + changeSignatureAlgorithm('user.info.response', $scope.userInfoSignedResponseAlg); + }; + + function changeSignatureAlgorithm(attrPrefix, attrValue) { + var attrName = attrPrefix + '.signature.alg'; + if (attrValue === 'unsigned') { + $scope.client.attributes[attrName] = null; + } else { + $scope.client.attributes[attrName] = attrValue; + } + } + + function getSignatureAlgorithm(attrPrefix) { + var attrName = attrPrefix + '.signature.alg'; + var attrVal = $scope.client.attributes[attrName]; + return attrVal==null ? 'unsigned' : attrVal; + } + $scope.$watch(function() { return $location.path(); }, function() { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html index 1378f787e4..af10a22d03 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html @@ -333,6 +333,23 @@ +
    + {{:: 'fine-oidc-endpoint-conf' | translate}} {{:: 'fine-oidc-endpoint-conf.tooltip' | translate}} +
    + +
    +
    + +
    +
    + {{:: 'user-info-signed-response-alg.tooltip' | translate}} +
    +
    +
    From 73bcfba5f3770c4d203981e9b51f58001098d337 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 30 Aug 2016 16:32:00 -0300 Subject: [PATCH 34/49] [KEYCLOAK-3337] - Support more specific date/time periods with the Time policy provider --- .../time/TimePolicyAdminResource.java | 8 ++-- .../provider/time/TimePolicyProvider.java | 42 ++++++++++++++++-- .../messages/admin-messages_en.properties | 10 +++++ .../resources/js/authz/authz-controller.js | 44 ++++++++++++++++++- .../resource-server-policy-time-detail.html | 44 ++++++++++++++++++- 5 files changed, 137 insertions(+), 11 deletions(-) diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyAdminResource.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyAdminResource.java index 75de911765..96c1d24d25 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyAdminResource.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyAdminResource.java @@ -37,12 +37,10 @@ public class TimePolicyAdminResource implements PolicyProviderAdminService { String nbf = policy.getConfig().get("nbf"); String noa = policy.getConfig().get("noa"); - if (nbf == null && noa == null) { - throw new RuntimeException("You must provide NotBefore, NotOnOrAfter or both."); + if (nbf != null && noa != null) { + validateFormat(nbf); + validateFormat(noa); } - - validateFormat(nbf); - validateFormat(noa); } @Override diff --git a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java index dc6af9f3da..3205cc5143 100644 --- a/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java +++ b/authz/policy/common/src/main/java/org/keycloak/authorization/policy/provider/time/TimePolicyProvider.java @@ -22,8 +22,11 @@ import org.keycloak.authorization.policy.evaluation.Evaluation; import org.keycloak.authorization.policy.provider.PolicyProvider; import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Date; +import static com.sun.corba.se.spi.activation.IIOP_CLEAR_TEXT.value; + /** * @author Pedro Igor */ @@ -45,9 +48,7 @@ public class TimePolicyProvider implements PolicyProvider { public void evaluate(Evaluation evaluation) { try { String notBefore = this.policy.getConfig().get("nbf"); - if (notBefore != null) { - if (this.currentDate.before(this.dateFormat.parse(format(notBefore)))) { evaluation.deny(); return; @@ -55,7 +56,6 @@ public class TimePolicyProvider implements PolicyProvider { } String notOnOrAfter = this.policy.getConfig().get("noa"); - if (notOnOrAfter != null) { if (this.currentDate.after(this.dateFormat.parse(format(notOnOrAfter)))) { evaluation.deny(); @@ -63,12 +63,48 @@ public class TimePolicyProvider implements PolicyProvider { } } + if (isInvalid(Calendar.DAY_OF_MONTH, "dayMonth") + || isInvalid(Calendar.MONTH, "month") + || isInvalid(Calendar.YEAR, "year") + || isInvalid(Calendar.HOUR_OF_DAY, "hour") + || isInvalid(Calendar.MINUTE, "minute")) { + evaluation.deny(); + return; + } + evaluation.grant(); } catch (Exception e) { throw new RuntimeException("Could not evaluate time-based policy [" + this.policy.getName() + "].", e); } } + private boolean isInvalid(int timeConstant, String configName) { + Calendar calendar = Calendar.getInstance(); + + calendar.setTime(this.currentDate); + + int dateField = calendar.get(timeConstant); + + if (Calendar.MONTH == timeConstant) { + dateField++; + } + + String start = this.policy.getConfig().get(configName); + if (start != null) { + String end = this.policy.getConfig().get(configName + "End"); + if (end != null) { + if (dateField < Integer.parseInt(start) || dateField > Integer.parseInt(end)) { + return true; + } + } else { + if (dateField != Integer.parseInt(start)) { + return true; + } + } + } + return false; + } + static String format(String notBefore) { String trimmed = notBefore.trim(); diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index a753ae9175..4b22e39363 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -1051,6 +1051,16 @@ authz-add-time-policy=Add Time Policy authz-policy-time-not-before.tooltip=Defines the time before which the policy MUST NOT be granted. Only granted if current date/time is after or equal to this value. authz-policy-time-not-on-after=Not On or After authz-policy-time-not-on-after.tooltip=Defines the time after which the policy MUST NOT be granted. Only granted if current date/time is before or equal to this value. +authz-policy-time-day-month=Day of Month +authz-policy-time-day-month.tooltip=Defines the day of month before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the day of month before/equal which the policy MUST be granted. In this case, the policy would be granted if current day of month is between/equal the two values you provided. +authz-policy-time-month=Month +authz-policy-time-month.tooltip=Defines the month before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the month before/equal which the policy MUST be granted. In this case, the policy would be granted if current month is between/equal the two values you provided. +authz-policy-time-year=Year +authz-policy-time-year.tooltip=Defines the year before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the year before/equal which the policy MUST be granted. In this case, the policy would be granted if current year is between/equal the two values you provided. +authz-policy-time-hour=Hour +authz-policy-time-hour.tooltip=Defines the hour before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the hour before/equal which the policy MUST be granted. In this case, the policy would be granted if current hour is between/equal the two values you provided. +authz-policy-time-minute=Minute +authz-policy-time-minute.tooltip=Defines the minute before/equal which policy MUST be granted. You can also provide a range period by filling the second field with the minute before/equal which the policy MUST be granted. In this case, the policy would be granted if current minute is between/equal the two values you provided. # Authz Drools Policy Detail authz-add-drools-policy=Add Drools Policy diff --git a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js index 9c67740e61..72b6c43c06 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/authz/authz-controller.js @@ -1090,7 +1090,36 @@ module.controller('ResourceServerPolicyTimeDetailCtrl', function($scope, $route, }, onInitUpdate : function(policy) { - + if (policy.config.dayMonth) { + policy.config.dayMonth = parseInt(policy.config.dayMonth); + } + if (policy.config.dayMonthEnd) { + policy.config.dayMonthEnd = parseInt(policy.config.dayMonthEnd); + } + if (policy.config.month) { + policy.config.month = parseInt(policy.config.month); + } + if (policy.config.monthEnd) { + policy.config.monthEnd = parseInt(policy.config.monthEnd); + } + if (policy.config.year) { + policy.config.year = parseInt(policy.config.year); + } + if (policy.config.yearEnd) { + policy.config.yearEnd = parseInt(policy.config.yearEnd); + } + if (policy.config.hour) { + policy.config.hour = parseInt(policy.config.hour); + } + if (policy.config.hourEnd) { + policy.config.hourEnd = parseInt(policy.config.hourEnd); + } + if (policy.config.minute) { + policy.config.minute = parseInt(policy.config.minute); + } + if (policy.config.minuteEnd) { + policy.config.minuteEnd = parseInt(policy.config.minuteEnd); + } }, onUpdate : function() { @@ -1106,6 +1135,19 @@ module.controller('ResourceServerPolicyTimeDetailCtrl', function($scope, $route, } }, realm, client, $scope); + + $scope.isRequired = function () { + var policy = $scope.policy; + if (policy.config.noa || policy.config.nbf + || policy.config.dayMonth + || policy.config.month + || policy.config.year + || policy.config.hour + || policy.config.minute) { + return false; + } + return true; + } }); module.controller('ResourceServerPolicyAggregateDetailCtrl', function($scope, $route, $location, realm, PolicyController, ResourceServerPolicy, client) { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-time-detail.html b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-time-detail.html index fc4af74f25..517773470f 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-time-detail.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/authz/policy/provider/resource-server-policy-time-detail.html @@ -37,7 +37,7 @@
    - +
    {{:: 'authz-policy-time-not-before.tooltip' | translate}}
    @@ -45,10 +45,50 @@
    - +
    {{:: 'authz-policy-time-not-on-after.tooltip' | translate}}
    +
    + + +
    +   to   +
    + {{:: 'authz-policy-time-day-month.tooltip' | translate}} +
    +
    + + +
    +   to   +
    + {{:: 'authz-policy-time-month.tooltip' | translate}} +
    +
    + + +
    +   to   +
    + {{:: 'authz-policy-time-year.tooltip' | translate}} +
    +
    + + +
    +   to   +
    + {{:: 'authz-policy-time-hour.tooltip' | translate}} +
    +
    + + +
    +   to   +
    + {{:: 'authz-policy-time-minute.tooltip' | translate}} +
    From 1bea93b1e11c0a41dc96b8df1eaa1d20367a706c Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Wed, 31 Aug 2016 12:05:49 +0200 Subject: [PATCH 35/49] KEYCLOAK-3501 Fix NPE in migration of OTP action --- .../java/org/keycloak/migration/migrators/MigrationUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java index 08da081b4e..2afbbcc1e8 100644 --- a/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java +++ b/server-spi/src/main/java/org/keycloak/migration/migrators/MigrationUtils.java @@ -49,8 +49,8 @@ public class MigrationUtils { public static void updateOTPRequiredAction(RequiredActionProviderModel otpAction) { if (otpAction == null) return; - if (!otpAction.getProviderId().equals(UserModel.RequiredAction.CONFIGURE_TOTP.name())) return; - if (!otpAction.getName().equals("Configure Totp")) return; + if (!UserModel.RequiredAction.CONFIGURE_TOTP.name().equals(otpAction.getProviderId())) return; + if (!"Configure Totp".equals(otpAction.getName())) return; otpAction.setName("Configure OTP"); } From 1b085d3e1397352ac45cb9118417fd31beaad871 Mon Sep 17 00:00:00 2001 From: Vaclav Muzikar Date: Wed, 31 Aug 2016 13:07:33 +0200 Subject: [PATCH 36/49] KEYCLOAK-3421 Validation for URI fragments in redirect_uri --- .../AbstractClientRegistrationProvider.java | 24 +++++ .../resources/admin/ClientResource.java | 18 +++- .../resources/admin/ClientsResource.java | 23 ++-- .../services/validation/ClientValidator.java | 54 ++++++++++ .../validation/ValidationMessage.java | 100 ++++++++++++++++++ .../validation/ValidationMessages.java | 83 +++++++++++++++ .../keycloak/testsuite/admin/ClientTest.java | 54 +++++++++- .../client/OIDCClientRegistrationTest.java | 54 +++++++--- .../admin/messages/messages_en.properties | 3 + .../admin/resources/js/controllers/clients.js | 12 +++ 10 files changed, 392 insertions(+), 33 deletions(-) create mode 100644 services/src/main/java/org/keycloak/services/validation/ClientValidator.java create mode 100644 services/src/main/java/org/keycloak/services/validation/ValidationMessage.java create mode 100644 services/src/main/java/org/keycloak/services/validation/ValidationMessages.java diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java index 88b5a340c1..d634b006b7 100755 --- a/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/AbstractClientRegistrationProvider.java @@ -29,8 +29,12 @@ import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ForbiddenException; +import org.keycloak.services.resources.admin.AdminRoot; +import org.keycloak.services.validation.ClientValidator; +import org.keycloak.services.validation.ValidationMessages; import javax.ws.rs.core.Response; +import java.util.Properties; /** * @author Stian Thorgersen @@ -50,6 +54,16 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist auth.requireCreate(); + ValidationMessages validationMessages = new ValidationMessages(); + if (!ClientValidator.validate(client, validationMessages)) { + String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA; + throw new ErrorResponseException( + errorCode, + validationMessages.getStringMessages(), + Response.Status.BAD_REQUEST + ); + } + try { ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true); @@ -104,6 +118,16 @@ public abstract class AbstractClientRegistrationProvider implements ClientRegist throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier modified", Response.Status.BAD_REQUEST); } + ValidationMessages validationMessages = new ValidationMessages(); + if (!ClientValidator.validate(rep, validationMessages)) { + String errorCode = validationMessages.fieldHasError("redirectUris") ? ErrorCodes.INVALID_REDIRECT_URI : ErrorCodes.INVALID_CLIENT_METADATA; + throw new ErrorResponseException( + errorCode, + validationMessages.getStringMessages(), + Response.Status.BAD_REQUEST + ); + } + RepresentationToModel.updateClient(rep, client); rep = ModelToRepresentation.toRepresentation(client); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index fc5b3c9e93..dd17b07a8e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -40,6 +40,7 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation; +import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ServicesLogger; import org.keycloak.services.clientregistration.ClientRegistrationTokenUtils; import org.keycloak.services.managers.ClientManager; @@ -48,6 +49,8 @@ import org.keycloak.services.managers.ResourceAdminManager; import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.ErrorResponse; import org.keycloak.common.util.Time; +import org.keycloak.services.validation.ClientValidator; +import org.keycloak.services.validation.ValidationMessages; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -63,10 +66,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import static java.lang.Boolean.TRUE; @@ -126,6 +126,16 @@ public class ClientResource { throw new NotFoundException("Could not find client"); } + ValidationMessages validationMessages = new ValidationMessages(); + if (!ClientValidator.validate(rep, validationMessages)) { + Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + throw new ErrorResponseException( + validationMessages.getStringMessages(), + validationMessages.getStringMessages(messages), + Response.Status.BAD_REQUEST + ); + } + try { updateClientFromRep(rep, client, session); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index 207f8d1fb5..dacea26b68 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -17,7 +17,6 @@ package org.keycloak.services.resources.admin; import org.jboss.resteasy.annotations.cache.NoCache; -import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; @@ -28,16 +27,13 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ServicesLogger; import org.keycloak.services.managers.ClientManager; +import org.keycloak.services.validation.ClientValidator; +import org.keycloak.services.validation.ValidationMessages; -import javax.ws.rs.Consumes; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -45,6 +41,7 @@ import javax.ws.rs.core.UriInfo; import java.util.ArrayList; import java.util.List; +import java.util.Properties; /** * Base resource class for managing a realm's clients. @@ -122,6 +119,16 @@ public class ClientsResource { public Response createClient(final @Context UriInfo uriInfo, final ClientRepresentation rep) { auth.requireManage(); + ValidationMessages validationMessages = new ValidationMessages(); + if (!ClientValidator.validate(rep, validationMessages)) { + Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + throw new ErrorResponseException( + validationMessages.getStringMessages(), + validationMessages.getStringMessages(messages), + Response.Status.BAD_REQUEST + ); + } + try { ClientModel clientModel = ClientManager.createClient(session, realm, rep, true); diff --git a/services/src/main/java/org/keycloak/services/validation/ClientValidator.java b/services/src/main/java/org/keycloak/services/validation/ClientValidator.java new file mode 100644 index 0000000000..22290c5a12 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/validation/ClientValidator.java @@ -0,0 +1,54 @@ +/* + * + * * Copyright 2016 Red Hat, Inc. and/or its affiliates + * * and other contributors as indicated by the @author tags. + * * + * * 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.services.validation; + +import org.keycloak.representations.idm.ClientRepresentation; + +/** + * @author Vaclav Muzikar + */ +public class ClientValidator { + /** + * Checks if the Client's Redirect URIs doesn't contain any URI fragments (like http://example.org/auth#fragment) + * + * @see KEYCLOAK-3421 + * @param client + * @param messages + * @return true if Redirect URIs doesn't contain any URI with fragments + */ + public static boolean validate(ClientRepresentation client, ValidationMessages messages) { + boolean isValid = true; + + if (client.getRedirectUris() != null) { + long urisWithFragmentCount = client.getRedirectUris().stream().filter(p -> p.contains("#")).count(); + if (urisWithFragmentCount > 0) { + messages.add("redirectUris", "Redirect URIs must not contain an URI fragment", "clientRedirectURIsFragmentError"); + isValid = false; + } + } + + if (client.getRootUrl() != null && client.getRootUrl().contains("#")) { + messages.add("rootUrl", "Root URL must not contain an URL fragment", "clientRootURLFragmentError"); + isValid = false; + } + + return isValid; + } +} diff --git a/services/src/main/java/org/keycloak/services/validation/ValidationMessage.java b/services/src/main/java/org/keycloak/services/validation/ValidationMessage.java new file mode 100644 index 0000000000..7e4dac12a5 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/validation/ValidationMessage.java @@ -0,0 +1,100 @@ +/* + * + * * Copyright 2016 Red Hat, Inc. and/or its affiliates + * * and other contributors as indicated by the @author tags. + * * + * * 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.services.validation; + +import java.text.MessageFormat; +import java.util.Properties; + +/** + * @author Vaclav Muzikar + */ +public class ValidationMessage { + private String fieldId; + private String message; + private String localizedMessageKey; + private Object[] localizedMessageParameters; + + public ValidationMessage(String message) { + this.message = message; + } + + public ValidationMessage(String message, String localizedMessageKey, Object... localizedMessageParameters) { + this.message = message; + this.localizedMessageKey = localizedMessageKey; + this.localizedMessageParameters = localizedMessageParameters; + } + + public String getFieldId() { + return fieldId; + } + + public void setFieldId(String fieldId) { + this.fieldId = fieldId; + } + + public String getLocalizedMessageKey() { + return localizedMessageKey; + } + + public void setLocalizedMessageKey(String localizedMessageKey) { + this.localizedMessageKey = localizedMessageKey; + } + + public Object[] getLocalizedMessageParameters() { + return localizedMessageParameters; + } + + public void setLocalizedMessageParameters(Object[] localizedMessageParameters) { + this.localizedMessageParameters = localizedMessageParameters; + } + + public String getMessage() { + return message; + } + + public String getMessage(Properties localizedMessages) { + if (getLocalizedMessageKey() != null) { + return MessageFormat.format(localizedMessages.getProperty(getLocalizedMessageKey(), getMessage()), getLocalizedMessageParameters()); + } + else { + return getMessage(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ValidationMessage message1 = (ValidationMessage) o; + + if (getFieldId() != null ? !getFieldId().equals(message1.getFieldId()) : message1.getFieldId() != null) + return false; + return getMessage() != null ? getMessage().equals(message1.getMessage()) : message1.getMessage() == null; + + } + + @Override + public int hashCode() { + int result = getFieldId() != null ? getFieldId().hashCode() : 0; + result = 31 * result + (getMessage() != null ? getMessage().hashCode() : 0); + return result; + } +} diff --git a/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java b/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java new file mode 100644 index 0000000000..e26ebff397 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/validation/ValidationMessages.java @@ -0,0 +1,83 @@ +/* + * + * * Copyright 2016 Red Hat, Inc. and/or its affiliates + * * and other contributors as indicated by the @author tags. + * * + * * 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.services.validation; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + + +/** + * @author Vaclav Muzikar + */ +public class ValidationMessages { + private Set messages = new LinkedHashSet<>(); + + public ValidationMessages() {} + + public ValidationMessages(String... messages) { + for (String message : messages) { + add(message); + } + } + + public void add(String message) { + messages.add(new ValidationMessage(message)); + } + + public void add(String message, String localizedMessageKey) { + messages.add(new ValidationMessage(message, localizedMessageKey)); + } + + public void add(String fieldId, String message, String localizedMessageKey) { + ValidationMessage validationMessage = new ValidationMessage(message, localizedMessageKey); + validationMessage.setFieldId(fieldId); + add(validationMessage); + } + + public void add(ValidationMessage message) { + messages.add(message); + } + + public boolean fieldHasError(String fieldId) { + for (ValidationMessage message : messages) { + if (message.getFieldId().equals(fieldId)) { + return true; + } + } + return false; + } + + public Set getMessages() { + return Collections.unmodifiableSet(messages); + } + + protected String getStringMessages(Function function) { + return messages.stream().map(function).collect(Collectors.joining("; ")); + } + + public String getStringMessages() { + return getStringMessages(ValidationMessage::getMessage); + } + + public String getStringMessages(Properties localizedMessages) { + return getStringMessages(x -> x.getMessage(localizedMessages)); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java index ee06750e93..b6f9bdea8b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ClientTest.java @@ -33,16 +33,14 @@ import org.keycloak.representations.adapters.action.PushNotBeforeAction; import org.keycloak.representations.adapters.action.TestAvailabilityAction; import org.keycloak.representations.idm.*; +import javax.ws.rs.BadRequestException; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import org.keycloak.services.ErrorResponseException; import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.util.AdminEventPaths; import org.keycloak.testsuite.util.ClientBuilder; @@ -212,6 +210,52 @@ public class ClientTest extends AbstractAdminTest { assertEquals("service-account-serviceclient", userRep.getUsername()); } + // KEYCLOAK-3421 + @Test + public void createClientWithFragments() { + ClientRepresentation client = ClientBuilder.create() + .clientId("client-with-fragment") + .rootUrl("http://localhost/base#someFragment") + .redirectUris("http://localhost/auth", "http://localhost/auth#fragment", "http://localhost/auth*", "/relative") + .build(); + + Response response = realm.clients().create(client); + assertUriFragmentError(response); + } + + // KEYCLOAK-3421 + @Test + public void updateClientWithFragments() { + ClientRepresentation client = ClientBuilder.create() + .clientId("client-with-fragment") + .redirectUris("http://localhost/auth", "http://localhost/auth*") + .build(); + Response response = realm.clients().create(client); + ClientResource clientResource = realm.clients().get(ApiUtil.getCreatedId(response)); + + client = clientResource.toRepresentation(); + client.setRootUrl("http://localhost/base#someFragment"); + List redirectUris = client.getRedirectUris(); + redirectUris.add("http://localhost/auth#fragment"); + redirectUris.add("/relative"); + client.setRedirectUris(redirectUris); + + try { + clientResource.update(client); + fail("Should fail"); + } + catch (BadRequestException e) { + assertUriFragmentError(e.getResponse()); + } + } + + private void assertUriFragmentError(Response response) { + assertEquals(response.getStatus(), 400); + String error = response.readEntity(OAuth2ErrorRepresentation.class).getError(); + assertTrue("Error response doesn't mention Redirect URIs fragments", error.contains("Redirect URIs must not contain an URI fragment")); + assertTrue("Error response doesn't mention Root URL fragments", error.contains("Root URL must not contain an URL fragment")); + } + @Test public void pushRevocation() { testingClient.testApp().clearAdminActions(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index 9c78c4b040..608d7a73c5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -102,6 +102,23 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { return response; } + private void assertCreateFail(OIDCClientRepresentation client, int expectedStatusCode) { + assertCreateFail(client, expectedStatusCode, null); + } + + private void assertCreateFail(OIDCClientRepresentation client, int expectedStatusCode, String expectedErrorContains) { + try { + reg.oidc().create(client); + Assert.fail("Not expected to successfuly register client"); + } catch (ClientRegistrationException expected) { + HttpErrorException httpEx = (HttpErrorException) expected.getCause(); + Assert.assertEquals(expectedStatusCode, httpEx.getStatusLine().getStatusCode()); + if (expectedErrorContains != null) { + assertTrue("Error response doesn't contain expected text", httpEx.getErrorResponse().contains(expectedErrorContains)); + } + } + } + @Test public void testCreateWithTrustedHost() throws Exception { reg.auth(null); @@ -109,17 +126,10 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { OIDCClientRepresentation client = createRep(); // Failed to create client - try { - reg.oidc().create(client); - Assert.fail("Not expected to successfuly register client"); - } catch (ClientRegistrationException expected) { - HttpErrorException httpEx = (HttpErrorException) expected.getCause(); - Assert.assertEquals(401, httpEx.getStatusLine().getStatusCode()); - } + assertCreateFail(client, 401); // Create trusted host entry - Response response = adminClient.realm(REALM_NAME).clientRegistrationTrustedHost().create(ClientRegistrationTrustedHostRepresentation.create("localhost", 2, 2)); - Assert.assertEquals(201, response.getStatus()); + createTrustedHost("localhost", 2); // Successfully register client reg.oidc().create(client); @@ -132,13 +142,20 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { reg.oidc().create(client); // Failed to create 3rd client - try { - reg.oidc().create(client); - Assert.fail("Not expected to successfuly register client"); - } catch (ClientRegistrationException expected) { - HttpErrorException httpEx = (HttpErrorException) expected.getCause(); - Assert.assertEquals(401, httpEx.getStatusLine().getStatusCode()); - } + assertCreateFail(client, 401); + } + + // KEYCLOAK-3421 + @Test + public void createClientWithUriFragment() { + reg.auth(null); + + createTrustedHost("localhost", 1); + + OIDCClientRepresentation client = createRep(); + client.setRedirectUris(Arrays.asList("http://localhost/auth", "http://localhost/auth#fragment", "http://localhost/auth*")); + + assertCreateFail(client, 400, "URI fragment"); } @Test @@ -323,4 +340,9 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest { } } + private void createTrustedHost(String name, int count) { + Response response = adminClient.realm(REALM_NAME).clientRegistrationTrustedHost().create(ClientRegistrationTrustedHostRepresentation.create(name, count, count)); + Assert.assertEquals(201, response.getStatus()); + } + } diff --git a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties index 95e16db33a..345cb2503c 100644 --- a/themes/src/main/resources/theme/base/admin/messages/messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/messages_en.properties @@ -12,3 +12,6 @@ ldapErrorMissingClientId=Client ID needs to be provided in config when Realm Rol ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Not possible to preserve group inheritance and use UID membership type together. ldapErrorCantWriteOnlyForReadOnlyLdap=Can't set write only when LDAP provider mode is not WRITABLE ldapErrorCantWriteOnlyAndReadOnly=Can't set write-only and read-only together + +clientRedirectURIsFragmentError=Redirect URIs must not contain an URI fragment +clientRootURLFragmentError=Root URL must not contain an URL fragment \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 3f13573653..24efd88e68 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -1111,6 +1111,12 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, templates, }, $scope.client, function() { $route.reload(); Notifications.success("Your changes have been saved to the client."); + }, function(error) { + if (error.status == 400 && error.data.error_description) { + Notifications.error(error.data.error_description); + } else { + Notifications.error('Unexpected error when updating client'); + } }); } }; @@ -1225,6 +1231,12 @@ module.controller('CreateClientCtrl', function($scope, realm, client, templates, var id = l.substring(l.lastIndexOf("/") + 1); $location.url("/realms/" + realm.realm + "/clients/" + id); Notifications.success("The client has been created."); + }, function(error) { + if (error.status == 400 && error.data.error_description) { + Notifications.error(error.data.error_description); + } else { + Notifications.error('Unexpected error when creating client'); + } }); }; From ce78cc1d1c753c0d5c84e427ae0ead185eb8c221 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Wed, 31 Aug 2016 21:04:36 -0300 Subject: [PATCH 37/49] [KEYCLOAK-3472] - Multiple paths with the same name and tests --- .../authorization/AbstractPolicyEnforcer.java | 3 +- .../authorization/PolicyEnforcer.java | 26 ++++++-- .../authz/photoz/photoz-restful-api/pom.xml | 6 ++ .../photoz/admin/AdminAlbumService.java | 6 +- .../example/photoz/album/AlbumService.java | 6 +- .../example/photoz/album/ProfileService.java | 4 +- .../example/photoz/util/Resources.java | 51 ++++++++++++++++ .../example/photoz/util/Transaction.java | 34 +++++++++++ .../photoz/util/TransactionInterceptor.java | 56 ++++++++++++++++++ .../src/main/resources/META-INF/beans.xml | 4 +- .../main/resources/META-INF/persistence.xml | 10 +++- .../META-INF/jboss-deployment-structure.xml | 1 + .../src/main/webapp/WEB-INF/photoz-ds.xml | 12 ---- .../photoz/photoz-restful-api/pom.xml | 6 ++ .../photoz/admin/AdminAlbumService.java | 6 +- .../example/photoz/album/AlbumService.java | 6 +- .../example/photoz/album/ProfileService.java | 4 +- .../example/photoz/util/Resources.java | 51 ++++++++++++++++ .../example/photoz/util/Transaction.java | 34 +++++++++++ .../photoz/util/TransactionInterceptor.java | 56 ++++++++++++++++++ .../src/main/resources/META-INF/beans.xml | 4 +- .../main/resources/META-INF/persistence.xml | 10 +++- .../META-INF/jboss-deployment-structure.xml | 1 + .../src/main/webapp/WEB-INF/photoz-ds.xml | 12 ---- .../authorization/EnforcerConfigTest.java | 52 ++++++++++++++++ .../enforcer-config-paths-same-name.json | 59 +++++++++++++++++++ .../authorization-test/test-authz-realm.json | 50 ++++++++++++++++ .../integration-arquillian/tests/pom.xml | 4 ++ 28 files changed, 521 insertions(+), 53 deletions(-) create mode 100644 examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Resources.java create mode 100644 examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Transaction.java create mode 100644 examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/TransactionInterceptor.java delete mode 100644 examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/photoz-ds.xml create mode 100644 testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Resources.java create mode 100644 testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Transaction.java create mode 100644 testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/TransactionInterceptor.java delete mode 100644 testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/photoz-ds.xml create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EnforcerConfigTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-paths-same-name.json create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/test-authz-realm.json diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java index cfe5ff9a97..bbbf573e34 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/AbstractPolicyEnforcer.java @@ -32,6 +32,7 @@ import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCon import org.keycloak.representations.idm.authorization.Permission; import java.net.URI; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -55,7 +56,7 @@ public abstract class AbstractPolicyEnforcer { this.enforcerConfig = policyEnforcer.getEnforcerConfig(); this.authzClient = policyEnforcer.getClient(); this.pathMatcher = new PathMatcher(); - this.paths = policyEnforcer.getPaths(); + this.paths = new ArrayList<>(policyEnforcer.getPaths()); } public AuthorizationContext authorize(OIDCHttpFacade httpFacade) { diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java index aa6d3d25d0..88ef9ce00e 100644 --- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java +++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/authorization/PolicyEnforcer.java @@ -33,8 +33,12 @@ import org.keycloak.representations.adapters.config.PolicyEnforcerConfig.PathCon import org.keycloak.representations.idm.authorization.Permission; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -95,8 +99,8 @@ public class PolicyEnforcer { return authzClient; } - List getPaths() { - return paths; + public List getPaths() { + return Collections.unmodifiableList(paths); } KeycloakDeployment getDeployment() { @@ -154,13 +158,27 @@ public class PolicyEnforcer { pathConfig.setId(registrationResponse.getId()); } else { - throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + ". Make sure you have created a resource on the server that matches with the path configuration."); + throw new RuntimeException("Could not find matching resource on server with uri [" + path + "] or name [" + resourceName + "]. Make sure you have created a resource on the server that matches with the path configuration."); } } else { pathConfig.setId(search.iterator().next()); } - paths.add(pathConfig); + PathConfig existingPath = null; + + for (PathConfig current : paths) { + if (current.getId().equals(pathConfig.getId()) && current.getPath().equals(pathConfig.getPath())) { + existingPath = current; + break; + } + } + + if (existingPath == null) { + paths.add(pathConfig); + } else { + existingPath.getMethods().addAll(pathConfig.getMethods()); + existingPath.getScopes().addAll(pathConfig.getScopes()); + } } return paths; diff --git a/examples/authz/photoz/photoz-restful-api/pom.xml b/examples/authz/photoz/photoz-restful-api/pom.xml index ba7a88079f..7622af4f85 100755 --- a/examples/authz/photoz/photoz-restful-api/pom.xml +++ b/examples/authz/photoz/photoz-restful-api/pom.xml @@ -39,6 +39,12 @@ 1.0.0.Final provided + + javax.enterprise + cdi-api + 1.0-SP4 + provided + org.keycloak keycloak-authz-client diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java index b349e026d4..428ba078bd 100644 --- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java +++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java @@ -19,9 +19,8 @@ package org.keycloak.example.photoz.admin; import org.keycloak.example.photoz.entity.Album; -import javax.ejb.Stateless; +import javax.inject.Inject; import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @@ -36,12 +35,11 @@ import java.util.List; * @author Pedro Igor */ @Path("/admin/album") -@Stateless public class AdminAlbumService { public static final String SCOPE_ADMIN_ALBUM_MANAGE = "urn:photoz.com:scopes:album:admin:manage"; - @PersistenceContext + @Inject private EntityManager entityManager; @Context diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java index a5d7f161b4..d0e9c2d5c5 100644 --- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java +++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java @@ -7,10 +7,12 @@ import org.keycloak.authorization.client.representation.ScopeRepresentation; import org.keycloak.authorization.client.resource.ProtectionResource; import org.keycloak.example.photoz.ErrorResponse; import org.keycloak.example.photoz.entity.Album; +import org.keycloak.example.photoz.util.Transaction; import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.util.JsonSerialization; import javax.ejb.Stateless; +import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; @@ -31,14 +33,14 @@ import java.util.List; import java.util.Set; @Path("/album") -@Stateless +@Transaction public class AlbumService { public static final String SCOPE_ALBUM_VIEW = "urn:photoz.com:scopes:album:view"; public static final String SCOPE_ALBUM_CREATE = "urn:photoz.com:scopes:album:create"; public static final String SCOPE_ALBUM_DELETE = "urn:photoz.com:scopes:album:delete"; - @PersistenceContext + @Inject private EntityManager entityManager; @Context diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java index be638b602e..f7b55cbc33 100644 --- a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java +++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java @@ -18,6 +18,7 @@ package org.keycloak.example.photoz.album; import javax.ejb.Stateless; +import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.servlet.http.HttpServletRequest; @@ -34,12 +35,11 @@ import java.util.List; * @author Pedro Igor */ @Path("/profile") -@Stateless public class ProfileService { private static final String PROFILE_VIEW = "urn:photoz.com:scopes:profile:view"; - @PersistenceContext + @Inject private EntityManager entityManager; @GET diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Resources.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Resources.java new file mode 100644 index 0000000000..c917da24db --- /dev/null +++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Resources.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.example.photoz.util; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Produces; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +/** + * @author Pedro Igor + */ +@ApplicationScoped +public class Resources { + + private EntityManagerFactory entityManagerFactory; + + @PostConstruct + public void init() { + entityManagerFactory = Persistence.createEntityManagerFactory("primary"); + } + + @PreDestroy + public void dispose() { + entityManagerFactory.close(); + } + + @RequestScoped + @Produces + public EntityManager createEntityManager() { + return entityManagerFactory.createEntityManager(); + } +} diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Transaction.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Transaction.java new file mode 100644 index 0000000000..a3caa780a8 --- /dev/null +++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Transaction.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.example.photoz.util; + +import javax.interceptor.InterceptorBinding; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Pedro Igor + */ +@InterceptorBinding +@Target({ TYPE }) +@Retention(RUNTIME) +public @interface Transaction { +} diff --git a/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/TransactionInterceptor.java b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/TransactionInterceptor.java new file mode 100644 index 0000000000..36d35f319b --- /dev/null +++ b/examples/authz/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/TransactionInterceptor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.example.photoz.util; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; + +/** + * @author Pedro Igor + */ +@Interceptor +@Transaction +public class TransactionInterceptor { + + @Inject + private Instance entityManager; + + @AroundInvoke + public Object aroundInvoke(InvocationContext context) { + EntityManager entityManager = this.entityManager.get(); + EntityTransaction transaction = entityManager.getTransaction(); + + try { + transaction.begin(); + Object proceed = context.proceed(); + transaction.commit(); + return proceed; + } catch (Exception cause) { + if (transaction != null && transaction.isActive()) { + transaction.rollback(); + } + throw new RuntimeException(cause); + } finally { + entityManager.close(); + } + } +} diff --git a/examples/authz/photoz/photoz-restful-api/src/main/resources/META-INF/beans.xml b/examples/authz/photoz/photoz-restful-api/src/main/resources/META-INF/beans.xml index 957dc8ac43..fbf2a32edf 100644 --- a/examples/authz/photoz/photoz-restful-api/src/main/resources/META-INF/beans.xml +++ b/examples/authz/photoz/photoz-restful-api/src/main/resources/META-INF/beans.xml @@ -3,5 +3,7 @@ xsi:schemaLocation=" http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> - + + org.keycloak.example.photoz.util.TransactionInterceptor + diff --git a/examples/authz/photoz/photoz-restful-api/src/main/resources/META-INF/persistence.xml b/examples/authz/photoz/photoz-restful-api/src/main/resources/META-INF/persistence.xml index 9323182405..c15d34f449 100644 --- a/examples/authz/photoz/photoz-restful-api/src/main/resources/META-INF/persistence.xml +++ b/examples/authz/photoz/photoz-restful-api/src/main/resources/META-INF/persistence.xml @@ -4,14 +4,18 @@ xsi:schemaLocation=" http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> - - java:jboss/datasources/PhotozDS + + org.hibernate.ejb.HibernatePersistence org.keycloak.example.photoz.entity.Album org.keycloak.example.photoz.entity.Photo - + + + + + diff --git a/examples/authz/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml b/examples/authz/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml index 4b23be62d7..455335898f 100644 --- a/examples/authz/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml +++ b/examples/authz/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml @@ -20,6 +20,7 @@ + diff --git a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/photoz-ds.xml b/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/photoz-ds.xml deleted file mode 100644 index 247448f2ae..0000000000 --- a/examples/authz/photoz/photoz-restful-api/src/main/webapp/WEB-INF/photoz-ds.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - jdbc:h2:${jboss.server.data.dir}/kc-authz-photo;AUTO_SERVER=TRUE - h2 - - sa - sa - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/pom.xml b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/pom.xml index 7885857cdd..f5e2d4c2b9 100755 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/pom.xml +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/pom.xml @@ -39,6 +39,12 @@ 1.0.0.Final provided + + javax.enterprise + cdi-api + 1.0-SP4 + provided + org.keycloak keycloak-authz-client diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java index b349e026d4..428ba078bd 100644 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/admin/AdminAlbumService.java @@ -19,9 +19,8 @@ package org.keycloak.example.photoz.admin; import org.keycloak.example.photoz.entity.Album; -import javax.ejb.Stateless; +import javax.inject.Inject; import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; @@ -36,12 +35,11 @@ import java.util.List; * @author Pedro Igor */ @Path("/admin/album") -@Stateless public class AdminAlbumService { public static final String SCOPE_ADMIN_ALBUM_MANAGE = "urn:photoz.com:scopes:album:admin:manage"; - @PersistenceContext + @Inject private EntityManager entityManager; @Context diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java index a5d7f161b4..d0e9c2d5c5 100644 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/AlbumService.java @@ -7,10 +7,12 @@ import org.keycloak.authorization.client.representation.ScopeRepresentation; import org.keycloak.authorization.client.resource.ProtectionResource; import org.keycloak.example.photoz.ErrorResponse; import org.keycloak.example.photoz.entity.Album; +import org.keycloak.example.photoz.util.Transaction; import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.util.JsonSerialization; import javax.ejb.Stateless; +import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; @@ -31,14 +33,14 @@ import java.util.List; import java.util.Set; @Path("/album") -@Stateless +@Transaction public class AlbumService { public static final String SCOPE_ALBUM_VIEW = "urn:photoz.com:scopes:album:view"; public static final String SCOPE_ALBUM_CREATE = "urn:photoz.com:scopes:album:create"; public static final String SCOPE_ALBUM_DELETE = "urn:photoz.com:scopes:album:delete"; - @PersistenceContext + @Inject private EntityManager entityManager; @Context diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java index be638b602e..f7b55cbc33 100644 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/album/ProfileService.java @@ -18,6 +18,7 @@ package org.keycloak.example.photoz.album; import javax.ejb.Stateless; +import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.servlet.http.HttpServletRequest; @@ -34,12 +35,11 @@ import java.util.List; * @author Pedro Igor */ @Path("/profile") -@Stateless public class ProfileService { private static final String PROFILE_VIEW = "urn:photoz.com:scopes:profile:view"; - @PersistenceContext + @Inject private EntityManager entityManager; @GET diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Resources.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Resources.java new file mode 100644 index 0000000000..c917da24db --- /dev/null +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Resources.java @@ -0,0 +1,51 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.example.photoz.util; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Produces; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +/** + * @author Pedro Igor + */ +@ApplicationScoped +public class Resources { + + private EntityManagerFactory entityManagerFactory; + + @PostConstruct + public void init() { + entityManagerFactory = Persistence.createEntityManagerFactory("primary"); + } + + @PreDestroy + public void dispose() { + entityManagerFactory.close(); + } + + @RequestScoped + @Produces + public EntityManager createEntityManager() { + return entityManagerFactory.createEntityManager(); + } +} diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Transaction.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Transaction.java new file mode 100644 index 0000000000..a3caa780a8 --- /dev/null +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/Transaction.java @@ -0,0 +1,34 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.example.photoz.util; + +import javax.interceptor.InterceptorBinding; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author Pedro Igor + */ +@InterceptorBinding +@Target({ TYPE }) +@Retention(RUNTIME) +public @interface Transaction { +} diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/TransactionInterceptor.java b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/TransactionInterceptor.java new file mode 100644 index 0000000000..36d35f319b --- /dev/null +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/java/org/keycloak/example/photoz/util/TransactionInterceptor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.example.photoz.util; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import javax.persistence.EntityManager; +import javax.persistence.EntityTransaction; + +/** + * @author Pedro Igor + */ +@Interceptor +@Transaction +public class TransactionInterceptor { + + @Inject + private Instance entityManager; + + @AroundInvoke + public Object aroundInvoke(InvocationContext context) { + EntityManager entityManager = this.entityManager.get(); + EntityTransaction transaction = entityManager.getTransaction(); + + try { + transaction.begin(); + Object proceed = context.proceed(); + transaction.commit(); + return proceed; + } catch (Exception cause) { + if (transaction != null && transaction.isActive()) { + transaction.rollback(); + } + throw new RuntimeException(cause); + } finally { + entityManager.close(); + } + } +} diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/META-INF/beans.xml b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/META-INF/beans.xml index 957dc8ac43..fbf2a32edf 100644 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/META-INF/beans.xml +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/META-INF/beans.xml @@ -3,5 +3,7 @@ xsi:schemaLocation=" http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd"> - + + org.keycloak.example.photoz.util.TransactionInterceptor + diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/META-INF/persistence.xml b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/META-INF/persistence.xml index 9323182405..8b6d226720 100644 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/META-INF/persistence.xml +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/resources/META-INF/persistence.xml @@ -4,14 +4,18 @@ xsi:schemaLocation=" http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> - - java:jboss/datasources/PhotozDS + + org.hibernate.ejb.HibernatePersistence org.keycloak.example.photoz.entity.Album org.keycloak.example.photoz.entity.Photo - + + + + + diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml index 4b23be62d7..455335898f 100644 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml +++ b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/META-INF/jboss-deployment-structure.xml @@ -20,6 +20,7 @@ + diff --git a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/photoz-ds.xml b/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/photoz-ds.xml deleted file mode 100644 index 247448f2ae..0000000000 --- a/testsuite/integration-arquillian/test-apps/photoz/photoz-restful-api/src/main/webapp/WEB-INF/photoz-ds.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - jdbc:h2:${jboss.server.data.dir}/kc-authz-photo;AUTO_SERVER=TRUE - h2 - - sa - sa - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EnforcerConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EnforcerConfigTest.java new file mode 100644 index 0000000000..4236b2e14f --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/authorization/EnforcerConfigTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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.adapter.example.authorization; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; +import org.keycloak.adapters.authorization.PolicyEnforcer; +import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.keycloak.testsuite.util.IOUtil.loadRealm; + +/** + * @author Pedro Igor + */ +public class EnforcerConfigTest extends AbstractKeycloakTest { + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation realm = loadRealm(getClass().getResourceAsStream("/authorization-test/test-authz-realm.json")); + testRealms.add(realm); + } + + @Test + public void testMultiplePathsWithSameName() throws Exception{ + KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(getClass().getResourceAsStream("/authorization-test/enforcer-config-paths-same-name.json")); + PolicyEnforcer policyEnforcer = deployment.getPolicyEnforcer(); + List paths = policyEnforcer.getPaths(); + assertEquals(1, paths.size()); + assertEquals(4, paths.get(0).getMethods().size()); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-paths-same-name.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-paths-same-name.json new file mode 100644 index 0000000000..19d8c55fe5 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/enforcer-config-paths-same-name.json @@ -0,0 +1,59 @@ +{ + "realm": "test-realm-authz", + "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "auth-server-url": "http://localhost:8180/auth", + "ssl-required": "external", + "resource": "test-app-authz", + "bearer-only": true, + "credentials": { + "secret": "secret" + }, + "policy-enforcer": { + "paths": [ + { + "path": "/v1/product/*", + "methods": [ + { + "method": "POST", + "scopes": [ + "create" + ] + } + ] + }, + { + "path": "/v1/product/*", + "methods": [ + { + "method": "GET", + "scopes": [ + "view" + ] + } + ] + }, + { + "path": "/v1/product/*", + "methods": [ + { + "method": "PUT", + "scopes": [ + "update" + ] + } + ] + }, + { + "path": "/v1/product/*", + "methods": [ + { + "method": "DELETE", + "scopes": [ + "delete" + ] + } + ] + } + ] + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/test-authz-realm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/test-authz-realm.json new file mode 100644 index 0000000000..b1a6f5a07f --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/authorization-test/test-authz-realm.json @@ -0,0 +1,50 @@ +{ + "id": "test-realm-authz", + "realm": "test-realm-authz", + "enabled": true, + "sslRequired": "external", + "privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=", + "publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB", + "requiredCredentials": [ "password" ], + "users": [ + { + "username": "service-account-test-app-authz", + "enabled": true, + "serviceAccountClientId": "test-app-authz", + "clientRoles": { + "test-app-authz" : ["uma_protection"] + } + } + ], + "clients": [ + { + "clientId": "test-app-authz", + "enabled": true, + "baseUrl": "/test-app-authz", + "adminUrl": "/test-app-authz", + "bearerOnly": false, + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Product Resource", + "uri": "/v1/product/*", + "scopes": [ + { + "name": "view", + "name": "create", + "name": "delete", + "name": "update" + } + ] + } + ] + }, + "redirectUris": [ + "/test-app-authz/*" + ], + "secret": "secret" + } + ] +} diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 40e17b5572..b2ee848b26 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -784,6 +784,10 @@ org.keycloak keycloak-saml-adapter-api-public + + org.keycloak + keycloak-authz-client + From c874f96228f13f7ca2fb6eff18c1016580887de7 Mon Sep 17 00:00:00 2001 From: Marek Baluch Date: Thu, 1 Sep 2016 10:17:08 +0200 Subject: [PATCH 38/49] A class which generates a single junit xml file. This file will be consumed by Polarion --- testsuite/integration-arquillian/pom.xml | 1 + .../integration-arquillian/test-utils/pom.xml | 34 +++ .../util/junit/AggregateResultsReporter.java | 269 ++++++++++++++++++ .../integration-arquillian/tests/pom.xml | 11 +- 4 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 testsuite/integration-arquillian/test-utils/pom.xml create mode 100644 testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index 977fba877c..4a4cdce18b 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -119,6 +119,7 @@ test-apps + test-utils servers tests diff --git a/testsuite/integration-arquillian/test-utils/pom.xml b/testsuite/integration-arquillian/test-utils/pom.xml new file mode 100644 index 0000000000..c03c56b334 --- /dev/null +++ b/testsuite/integration-arquillian/test-utils/pom.xml @@ -0,0 +1,34 @@ + + + + integration-arquillian + org.keycloak.testsuite + 2.2.0-SNAPSHOT + + 4.0.0 + + integration-arquillian-test-utils + jar + + Test utils + + + + junit + junit + compile + + + org.jboss.logging + jboss-logging + + + commons-configuration + commons-configuration + 1.10 + + + + \ No newline at end of file diff --git a/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java b/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java new file mode 100644 index 0000000000..a0809c126c --- /dev/null +++ b/testsuite/integration-arquillian/test-utils/src/main/java/org/keycloak/testsuite/util/junit/AggregateResultsReporter.java @@ -0,0 +1,269 @@ +package org.keycloak.testsuite.util.junit; + +import org.apache.commons.configuration.PropertiesConfiguration; + +import org.jboss.logging.Logger; + +import org.junit.Ignore; +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Aggregates jUnit test results into a single report - XML file. + */ +public class AggregateResultsReporter extends RunListener { + + private static final Logger LOGGER = Logger.getLogger(AggregateResultsReporter.class); + + private final Document xml; + private final File reportFile; + private final boolean working; + + private final AtomicInteger tests = new AtomicInteger(0); + private final AtomicInteger errors = new AtomicInteger(0); + private final AtomicInteger failures = new AtomicInteger(0); + private final AtomicInteger ignored = new AtomicInteger(0); + private final AtomicLong suiteStartTime = new AtomicLong(0L); + + private final AtomicReference testsuite = new AtomicReference(); + + private final Map testTimes = new HashMap(); + + public AggregateResultsReporter() { + boolean working = true; + Document xml = null; + try { + xml = createEmptyDocument(); + } catch (ParserConfigurationException ex) { + LOGGER.error("Failed to create XML DOM - reporting will not be done", ex); + working = false; + } + + File reportFile = null; + try { + reportFile = createReportFile(); + } catch (Exception ex) { + LOGGER.error("Failed to create log file - reporting will not be done", ex); + working = false; + } + + this.working = working; + this.xml = xml; + this.reportFile = reportFile; + } + + private Document createEmptyDocument() throws ParserConfigurationException { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + return builder.newDocument(); + } + + private File createReportFile() throws Exception { + PropertiesConfiguration config = new PropertiesConfiguration(System.getProperty("testsuite.constants")); + config.setThrowExceptionOnMissing(true); + + final File logDir = new File(config.getString("log-dir")); + logDir.mkdirs(); + + final File reportFile = new File(logDir, "junit-report.xml").getAbsoluteFile(); + reportFile.createNewFile(); + + return reportFile; + } + + @Override + public void testRunStarted(Description description) throws Exception { + if (working) { + suiteStartTime.set(System.currentTimeMillis()); + + Element testsuite = xml.createElement("testsuite"); + + if (description.getChildren().size() == 1) { + testsuite.setAttribute("name", safeString(description.getChildren().get(0).getDisplayName())); + } + + xml.appendChild(testsuite); + this.testsuite.set(testsuite); + writeXml(); + } + } + + @Override + public void testStarted(Description description) throws Exception { + if (working) { + testTimes.put(description.getDisplayName(), System.currentTimeMillis()); + } + } + + @Override + public void testFinished(Description description) throws Exception { + if (working) { + if (testTimes.containsKey(description.getDisplayName())) { + testsuite.get().appendChild(createTestCase(description)); + writeXml(); + } + } + } + + @Override + public void testAssumptionFailure(Failure failure) { + if (working) { + ignored.incrementAndGet(); + + Element testcase = createTestCase(failure.getDescription()); + Element skipped = xml.createElement("skipped"); + skipped.setAttribute("message", safeString(failure.getMessage())); + + testcase.appendChild(skipped); + + testsuite.get().appendChild(testcase); + writeXml(); + } + } + + @Override + public void testFailure(Failure failure) throws Exception { + if (working) { + if (failure.getDescription().getMethodName() == null) { + // before class failed + for (Description child : failure.getDescription().getChildren()) { + // mark all methods failed + testFailure(new Failure(child, failure.getException())); + } + } else { + // normal failure + Element testcase = createTestCase(failure.getDescription()); + + Element element; + if (failure.getException() instanceof AssertionError) { + failures.incrementAndGet(); + element = xml.createElement("failure"); + } else { + errors.incrementAndGet(); + element = xml.createElement("error"); + } + + testcase.appendChild(element); + + element.setAttribute("type", safeString(failure.getException().getClass().getName())); + element.setAttribute("message", safeString(failure.getMessage())); + element.appendChild(xml.createCDATASection(safeString(failure.getTrace()))); + + testsuite.get().appendChild(testcase); + writeXml(); + } + } + } + + @Override + public void testIgnored(Description description) throws Exception { + if (working) { + ignored.incrementAndGet(); + + Element testcase = createTestCase(description); + + Element skipped = xml.createElement("skipped"); + skipped.setAttribute("message", safeString(description.getAnnotation(Ignore.class).value())); + + testcase.appendChild(skipped); + + testsuite.get().appendChild(testcase); + writeXml(); + } + } + + @Override + public void testRunFinished(Result result) throws Exception { + if (working) { + writeXml(); + } + } + + private void writeXml() { + Element testsuite = this.testsuite.get(); + + testsuite.setAttribute("tests", Integer.toString(tests.get())); + testsuite.setAttribute("errors", Integer.toString(errors.get())); + testsuite.setAttribute("skipped", Integer.toString(ignored.get())); + testsuite.setAttribute("failures", Integer.toString(failures.get())); + testsuite.setAttribute("time", computeTestTime(suiteStartTime.get())); + + try { + Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(reportFile, false), Charset.forName("UTF-8"))); + try { + Transformer t = TransformerFactory.newInstance().newTransformer(); + t.setOutputProperty(OutputKeys.INDENT, "yes"); + t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + t.transform(new DOMSource(xml), new StreamResult(writer)); + } catch (TransformerConfigurationException ex) { + LOGGER.error("Misconfigured transformer", ex); + } catch (TransformerException ex) { + LOGGER.error("Unable to save XML file", ex); + } finally { + writer.close(); + } + } catch (IOException ex) { + LOGGER.warn("Unable to open report file", ex); + } + } + + private String computeTestTime(Long startTime) { + if (startTime == null) { + return "0"; + } else { + long amount = System.currentTimeMillis() - startTime; + return String.format("%.3f", amount / 1000F); + } + } + + private Element createTestCase(Description description) { + tests.incrementAndGet(); + + Element testcase = xml.createElement("testcase"); + + testcase.setAttribute("name", safeString(description.getMethodName())); + testcase.setAttribute("classname", safeString(description.getClassName())); + testcase.setAttribute("time", computeTestTime(testTimes.remove(description.getDisplayName()))); + + return testcase; + } + + private String safeString(String input) { + if (input == null) { + return "null"; + } + + return input + // first remove color coding (all of it) + .replaceAll("\u001b\\[\\d+m", "") + // then remove control characters that are not whitespaces + .replaceAll("[\\p{Cntrl}&&[^\\p{Space}]]", ""); + } +} diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 6c250598a4..0a1fddfe6d 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -37,7 +37,7 @@ base other - + undertow true @@ -178,7 +178,7 @@ listener - org.keycloak.testsuite.util.TestEventsLogger + org.keycloak.testsuite.util.TestEventsLogger,org.keycloak.testsuite.util.junit.AggregateResultsReporter @@ -645,6 +645,13 @@ + + + org.keycloak.testsuite + integration-arquillian-test-utils + ${project.version} + + junit From b17bd9e6607a3dbb431d04173a203e1caebefaaf Mon Sep 17 00:00:00 2001 From: Mohit Suman Date: Fri, 2 Sep 2016 01:37:10 +0530 Subject: [PATCH 39/49] Fixed dropdown menu in navigation bar in small screens --- .../base/admin/resources/partials/menu.html | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/menu.html b/themes/src/main/resources/theme/base/admin/resources/partials/menu.html index 85a9a1c3bc..8523142f99 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/menu.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/menu.html @@ -1,13 +1,14 @@ - +
    + +
    +
    + +
    +
    + {{:: 'request-object-signature-alg.tooltip' | translate}} +
    From fc6b7ea8ee2a4263dc352032a0332b3291d9b658 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 5 Sep 2016 09:45:44 +0200 Subject: [PATCH 42/49] Enabled Norwegian translation --- themes/src/main/resources/theme/base/account/theme.properties | 2 +- themes/src/main/resources/theme/base/admin/theme.properties | 2 +- themes/src/main/resources/theme/base/email/theme.properties | 2 +- themes/src/main/resources/theme/base/login/theme.properties | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/themes/src/main/resources/theme/base/account/theme.properties b/themes/src/main/resources/theme/base/account/theme.properties index 2f853e89da..deb2dc91b6 100644 --- a/themes/src/main/resources/theme/base/account/theme.properties +++ b/themes/src/main/resources/theme/base/account/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,pt-BR,ru \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,pt-BR,ru,no \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/theme.properties b/themes/src/main/resources/theme/base/admin/theme.properties index 6b8008cc09..0533ca8452 100644 --- a/themes/src/main/resources/theme/base/admin/theme.properties +++ b/themes/src/main/resources/theme/base/admin/theme.properties @@ -1,2 +1,2 @@ import=common/keycloak -locales=ca,de,en,es,fr,it,ja,pt-BR,ru \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,pt-BR,ru,no \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/email/theme.properties b/themes/src/main/resources/theme/base/email/theme.properties index 2f853e89da..deb2dc91b6 100644 --- a/themes/src/main/resources/theme/base/email/theme.properties +++ b/themes/src/main/resources/theme/base/email/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,pt-BR,ru \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,pt-BR,ru,no \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/login/theme.properties b/themes/src/main/resources/theme/base/login/theme.properties index 2f853e89da..deb2dc91b6 100644 --- a/themes/src/main/resources/theme/base/login/theme.properties +++ b/themes/src/main/resources/theme/base/login/theme.properties @@ -1 +1 @@ -locales=ca,de,en,es,fr,it,ja,pt-BR,ru \ No newline at end of file +locales=ca,de,en,es,fr,it,ja,pt-BR,ru,no \ No newline at end of file From 39fe43957317830d578387e69f1b8f49585fc54b Mon Sep 17 00:00:00 2001 From: Vlasta Ramik Date: Fri, 2 Sep 2016 13:55:49 +0200 Subject: [PATCH 43/49] Database migration testing --- testsuite/integration-arquillian/pom.xml | 4 + .../jboss/common/datasource-jdbc-url.xsl | 6 +- .../auth-server/jboss/common/datasource.xsl | 8 +- .../migration/{wildfly_kc12 => }/assembly.xml | 6 +- .../servers/migration/pom.xml | 424 +++--- .../src/main/xslt/add-dialect-logger.xsl | 0 .../src/main/xslt/datasource.xsl | 7 +- .../src/main/xslt/module.xsl | 3 +- .../servers/migration/wildfly_kc12/pom.xml | 61 - .../wildfly_kc12/src/main/xslt/datasource.xsl | 111 -- .../wildfly_kc12/src/main/xslt/module.xsl | 50 - .../migration/wildfly_kc13/assembly.xml | 46 - .../servers/migration/wildfly_kc13/pom.xml | 62 - .../src/main/xslt/add-dialect-logger.xsl | 45 - .../wildfly_kc13/src/main/xslt/datasource.xsl | 111 -- .../migration/wildfly_kc14/assembly.xml | 46 - .../servers/migration/wildfly_kc14/pom.xml | 61 - .../src/main/xslt/add-dialect-logger.xsl | 45 - .../wildfly_kc14/src/main/xslt/datasource.xsl | 111 -- .../wildfly_kc14/src/main/xslt/module.xsl | 50 - .../migration/wildfly_kc15/assembly.xml | 46 - .../servers/migration/wildfly_kc15/pom.xml | 61 - .../src/main/xslt/add-dialect-logger.xsl | 45 - .../wildfly_kc15/src/main/xslt/module.xsl | 50 - .../migration/wildfly_kc16/assembly.xml | 46 - .../servers/migration/wildfly_kc16/pom.xml | 61 - .../src/main/xslt/add-dialect-logger.xsl | 45 - .../wildfly_kc16/src/main/xslt/datasource.xsl | 111 -- .../wildfly_kc16/src/main/xslt/module.xsl | 50 - .../integration-arquillian/tests/base/pom.xml | 4 +- .../arquillian/AuthServerTestEnricher.java | 40 +- .../testsuite/arquillian/SuiteContext.java | 3 + .../MigrationTestExecutionDecider.java | 2 +- .../testsuite/migration/MigrationTest.java | 71 +- .../base/src/test/resources/arquillian.xml | 62 +- ....json => migration-realm-1.9.8.Final.json} | 1224 ++++++++++------- .../migration-test/migration-realm-15.json | 751 ---------- .../integration-arquillian/tests/pom.xml | 179 +-- 38 files changed, 997 insertions(+), 3111 deletions(-) rename testsuite/integration-arquillian/servers/migration/{wildfly_kc12 => }/assembly.xml (84%) rename testsuite/integration-arquillian/servers/migration/{wildfly_kc12 => }/src/main/xslt/add-dialect-logger.xsl (100%) rename testsuite/integration-arquillian/servers/migration/{wildfly_kc15 => }/src/main/xslt/datasource.xsl (93%) rename testsuite/integration-arquillian/servers/migration/{wildfly_kc13 => }/src/main/xslt/module.xsl (94%) delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/datasource.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/module.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc13/assembly.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/add-dialect-logger.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/datasource.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc14/assembly.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/add-dialect-logger.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/datasource.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/module.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/add-dialect-logger.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/module.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc16/assembly.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/add-dialect-logger.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/datasource.xsl delete mode 100644 testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/module.xsl rename testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/{migration-realm-16.json => migration-realm-1.9.8.Final.json} (57%) delete mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index 977fba877c..aec76c16da 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -113,6 +113,10 @@ xml-maven-plugin 1.0.1 + + maven-resources-plugin + 3.0.0 + diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl index 589ee4cc1d..445b9733ff 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource-jdbc-url.xsl @@ -1,11 +1,7 @@ + exclude-result-prefixes="xalan"> diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl index e42a21c725..37ce56f4d0 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/datasource.xsl @@ -17,12 +17,8 @@ + exclude-result-prefixes="xalan"> @@ -47,7 +43,7 @@ - + diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/assembly.xml b/testsuite/integration-arquillian/servers/migration/assembly.xml similarity index 84% rename from testsuite/integration-arquillian/servers/migration/wildfly_kc12/assembly.xml rename to testsuite/integration-arquillian/servers/migration/assembly.xml index cc9969730e..e1e853da91 100644 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/assembly.xml +++ b/testsuite/integration-arquillian/servers/migration/assembly.xml @@ -17,7 +17,7 @@ - auth-server-jboss-kc14 + auth-server-jboss-${migrated.auth.server.version} zip @@ -28,14 +28,14 @@ ${keycloak.server.home} - keycloak-1.2.0.Final + keycloak-${migrated.auth.server.version} **/*.sh ${keycloak.server.home} - keycloak-1.2.0.Final + keycloak-${migrated.auth.server.version} **/*.sh diff --git a/testsuite/integration-arquillian/servers/migration/pom.xml b/testsuite/integration-arquillian/servers/migration/pom.xml index 6f6301ae0b..b7a5ff917e 100644 --- a/testsuite/integration-arquillian/servers/migration/pom.xml +++ b/testsuite/integration-arquillian/servers/migration/pom.xml @@ -1,20 +1,20 @@ +~ Copyright 2016 Red Hat, Inc. and/or its affiliates +~ and other contributors as indicated by the @author tags. +~ +~ 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. +--> @@ -25,231 +25,195 @@ 4.0.0 - integration-arquillian-migration-servers + integration-arquillian-migration-server pom - Migration Servers + Migration Server + + + ${project.build.directory}/unpacked/keycloak-${migrated.auth.server.version} + ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main + - - - migration-kc16 - - wildfly_kc16 - - - - migration-kc15 - - wildfly_kc15 - - - - migration-kc14 - - wildfly_kc14 - - - - migration-kc13 - - wildfly_kc13 - - - - migration-kc12 - - wildfly_kc12 - - - - org.apache.maven.plugins - maven-enforcer-plugin + maven-deploy-plugin true + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce-properties + + enforce + + + + + migrated.auth.server.version + + + jdbc.mvn.groupId + + + jdbc.mvn.artifactId + + + jdbc.mvn.version + + + keycloak.connectionsJpa.url + + + keycloak.connectionsJpa.user + + + keycloak.connectionsJpa.password + + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-server + generate-resources + + unpack + + + + + org.keycloak + keycloak-server-dist + ${migrated.auth.server.version} + zip + ${project.build.directory}/unpacked + + + + + + jdbc-driver + process-resources + + copy + + + + + ${jdbc.mvn.groupId} + ${jdbc.mvn.artifactId} + ${jdbc.mvn.version} + jar + + + ${jdbc.mvn.driver.deployment.dir} + true + + + + + + org.codehaus.mojo + xml-maven-plugin + + + configure-wildfly-datasource + process-resources + + transform + + + + + + ${keycloak.server.home}/modules/system/layers/base/com/h2database/h2/main + src/main/xslt/module.xsl + + module.xml + + ${jdbc.mvn.driver.deployment.dir} + + + database + ${jdbc.mvn.artifactId} + + + version + ${jdbc.mvn.version} + + + + + + ${keycloak.server.home}/standalone/configuration + src/main/xslt/datasource.xsl + + standalone.xml + + ${keycloak.server.home}/standalone/configuration + + + jdbc.url + ${keycloak.connectionsJpa.url} + + + driver + ${jdbc.mvn.artifactId} + + + username + ${keycloak.connectionsJpa.user} + + + password + ${keycloak.connectionsJpa.password} + + + + + + ${keycloak.server.home}/standalone/configuration + src/main/xslt/add-dialect-logger.xsl + + standalone.xml + + ${keycloak.server.home}/standalone/configuration + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + create-zip + package + + single + + + + assembly.xml + + false + + + + - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - enforce-properties - - enforce - - - - - jdbc.mvn.groupId - - - jdbc.mvn.artifactId - - - jdbc.mvn.version - - - keycloak.connectionsJpa.url - - - keycloak.connectionsJpa.user - - - keycloak.connectionsJpa.password - - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack-server - generate-resources - - unpack - - - - - org.keycloak - keycloak-server-dist - ${server.version} - zip - ${project.build.directory}/unpacked - - - - - - jdbc-driver - process-resources - - copy - - - - - ${jdbc.mvn.groupId} - ${jdbc.mvn.artifactId} - ${jdbc.mvn.version} - jar - - - ${jdbc.mvn.driver.deployment.dir} - true - - - - - - org.codehaus.mojo - xml-maven-plugin - - - configure-wildfly-datasource - process-resources - - transform - - - - - - ${keycloak.server.home}/modules/system/layers/base/com/h2database/h2/main - src/main/xslt/module.xsl - - module.xml - - ${jdbc.mvn.driver.deployment.dir} - - - database - ${jdbc.mvn.artifactId} - - - version - ${jdbc.mvn.version} - - - - - - ${keycloak.server.home}/standalone/configuration - src/main/xslt/datasource.xsl - - standalone.xml - - ${keycloak.server.home}/standalone/configuration - - - jdbc.url - ${keycloak.connectionsJpa.url} - - - driver - ${jdbc.mvn.artifactId} - - - username - ${keycloak.connectionsJpa.user} - - - password - ${keycloak.connectionsJpa.password} - - - - - - ${keycloak.server.home}/standalone/configuration - src/main/xslt/add-dialect-logger.xsl - - standalone.xml - - ${keycloak.server.home}/standalone/configuration - - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - create-zip - package - - single - - - - assembly.xml - - false - - - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/src/main/xslt/add-dialect-logger.xsl similarity index 100% rename from testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/add-dialect-logger.xsl rename to testsuite/integration-arquillian/servers/migration/src/main/xslt/add-dialect-logger.xsl diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/src/main/xslt/datasource.xsl similarity index 93% rename from testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/datasource.xsl rename to testsuite/integration-arquillian/servers/migration/src/main/xslt/datasource.xsl index 1fc4d87e7f..b68a3b1d41 100644 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/datasource.xsl +++ b/testsuite/integration-arquillian/servers/migration/src/main/xslt/datasource.xsl @@ -17,17 +17,12 @@ + exclude-result-prefixes="xalan"> - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/src/main/xslt/module.xsl similarity index 94% rename from testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/module.xsl rename to testsuite/integration-arquillian/servers/migration/src/main/xslt/module.xsl index f57019bdcd..766dd82013 100644 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/module.xsl +++ b/testsuite/integration-arquillian/servers/migration/src/main/xslt/module.xsl @@ -17,9 +17,8 @@ + exclude-result-prefixes="xalan"> diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml deleted file mode 100644 index 00407cadad..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc12 - pom - Keycloak 1.2.0.Final on Wildfly - - - 1.2.0.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/datasource.xsl deleted file mode 100644 index 1fc4d87e7f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/datasource.xsl +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/module.xsl deleted file mode 100644 index f57019bdcd..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc12/src/main/xslt/module.xsl +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/assembly.xml deleted file mode 100644 index cd48ba2349..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/assembly.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - auth-server-jboss-kc14 - - - zip - - - false - - - - ${keycloak.server.home} - keycloak-1.3.1.Final - - **/*.sh - - - - ${keycloak.server.home} - keycloak-1.3.1.Final - - **/*.sh - - 0755 - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml deleted file mode 100644 index 85124514d5..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc13 - pom - Keycloak 1.3.1.Final on Wildfly - - - 1.3.1.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/add-dialect-logger.xsl deleted file mode 100644 index 6b9e94a1ce..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/add-dialect-logger.xsl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/datasource.xsl deleted file mode 100644 index 1fc4d87e7f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc13/src/main/xslt/datasource.xsl +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/assembly.xml deleted file mode 100644 index deac59de4f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/assembly.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - auth-server-jboss-kc14 - - - zip - - - false - - - - ${keycloak.server.home} - keycloak-1.4.0.Final - - **/*.sh - - - - ${keycloak.server.home} - keycloak-1.4.0.Final - - **/*.sh - - 0755 - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml deleted file mode 100644 index baa4b88ea3..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc14 - pom - Keycloak 1.4.0.Final on Wildfly - - - 1.4.0.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/add-dialect-logger.xsl deleted file mode 100644 index 6b9e94a1ce..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/add-dialect-logger.xsl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/datasource.xsl deleted file mode 100644 index 1fc4d87e7f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/datasource.xsl +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/module.xsl deleted file mode 100644 index f57019bdcd..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc14/src/main/xslt/module.xsl +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml deleted file mode 100644 index b7330e32b2..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/assembly.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - auth-server-jboss-kc15 - - - zip - - - false - - - - ${keycloak.server.home} - keycloak-1.5.1.Final - - **/*.sh - - - - ${keycloak.server.home} - keycloak-1.5.1.Final - - **/*.sh - - 0755 - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml deleted file mode 100644 index 5928cd41c6..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc15 - pom - Keycloak 1.5.1.Final on Wildfly - - - 1.5.1.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/add-dialect-logger.xsl deleted file mode 100644 index 6b9e94a1ce..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/add-dialect-logger.xsl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/module.xsl deleted file mode 100644 index f57019bdcd..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc15/src/main/xslt/module.xsl +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/assembly.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/assembly.xml deleted file mode 100644 index 2e08956694..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/assembly.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - auth-server-jboss-kc16 - - - zip - - - false - - - - ${keycloak.server.home} - keycloak-1.6.1.Final - - **/*.sh - - - - ${keycloak.server.home} - keycloak-1.6.1.Final - - **/*.sh - - 0755 - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml deleted file mode 100644 index a919f053ee..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - org.keycloak.testsuite - integration-arquillian-migration-servers - 2.2.0-SNAPSHOT - - 4.0.0 - - integration-arquillian-server-wildfly-kc16 - pom - Keycloak 1.6.1.Final on Wildfly - - - 1.6.1.Final - ${project.build.directory}/unpacked/keycloak-${server.version} - ${keycloak.server.home}/modules/system/layers/base/com/${jdbc.mvn.artifactId}/main - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - false - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.codehaus.mojo - xml-maven-plugin - - - org.apache.maven.plugins - maven-assembly-plugin - - - - diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/add-dialect-logger.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/add-dialect-logger.xsl deleted file mode 100644 index 6b9e94a1ce..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/add-dialect-logger.xsl +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/datasource.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/datasource.xsl deleted file mode 100644 index 1fc4d87e7f..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/datasource.xsl +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/module.xsl b/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/module.xsl deleted file mode 100644 index f57019bdcd..0000000000 --- a/testsuite/integration-arquillian/servers/migration/wildfly_kc16/src/main/xslt/module.xsl +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index ac85ebd650..15594dae28 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -36,8 +36,6 @@ - - - - - **/migration/**/*Test.java **/cluster/**/*Test.java @@ -102,7 +100,6 @@ ${exclude.console} ${exclude.account} ${exclude.client} - ${exclude.migration} ${exclude.cluster} @@ -137,6 +134,7 @@ copy-resources + ${skip.add.user.json} ${auth.server.config.dir} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java index 984ba7de4f..d958e6e8eb 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java @@ -26,7 +26,7 @@ import org.jboss.arquillian.container.spi.Container; import org.jboss.arquillian.container.spi.ContainerRegistry; import org.jboss.arquillian.container.spi.event.StartContainer; import org.jboss.arquillian.container.spi.event.StartSuiteContainers; -import org.jboss.arquillian.container.test.api.ContainerController; +import org.jboss.arquillian.container.spi.event.StopContainer; import org.jboss.arquillian.core.api.Event; import org.jboss.arquillian.core.api.Instance; import org.jboss.arquillian.core.api.InstanceProducer; @@ -52,11 +52,10 @@ public class AuthServerTestEnricher { @Inject private Instance containerRegistry; - @Inject - private Instance containerController; - @Inject private Event startContainerEvent; + @Inject + private Event stopContainerEvent; private static final String AUTH_SERVER_CONTAINER_DEFAULT = "auth-server-undertow"; private static final String AUTH_SERVER_CONTAINER_PROPERTY = "auth.server.container"; @@ -65,8 +64,8 @@ public class AuthServerTestEnricher { private static final String AUTH_SERVER_CLUSTER_PROPERTY = "auth.server.cluster"; public static final boolean AUTH_SERVER_CLUSTER = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CLUSTER_PROPERTY, "false")); - private static final String MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY = "migrated.auth.server.container"; - public static final String MIGRATED_AUTH_SERVER_CONTAINER = System.getProperty(MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY); // == null if migration not enabled + private static final String MIGRATION_PROPERTY = "auth.server.jboss.migration"; + private static final Boolean MIGRATION_ENABLED = Boolean.parseBoolean(System.getProperty(MIGRATION_PROPERTY)); @Inject @SuiteScoped @@ -132,23 +131,19 @@ public class AuthServerTestEnricher { throw new RuntimeException(String.format("No auth server container matching '%sN' found in arquillian.xml.", authServerBackend)); } - if (MIGRATED_AUTH_SERVER_CONTAINER != null) { + if (MIGRATION_ENABLED) { // init migratedAuthServerInfo - if (MIGRATED_AUTH_SERVER_CONTAINER.startsWith("migrated-auth-server-")) { - for (ContainerInfo container : suiteContext.getContainers()) { - // migrated auth server - if (container.getQualifier().equals(MIGRATED_AUTH_SERVER_CONTAINER)) { - updateWithAuthServerInfo(container); - suiteContext.setMigratedAuthServerInfo(container); - } + for (ContainerInfo container : suiteContext.getContainers()) { + // migrated auth server + if (container.getQualifier().equals("auth-server-jboss-migration")) { + updateWithAuthServerInfo(container); + suiteContext.setMigratedAuthServerInfo(container); } - } else { - throw new IllegalArgumentException(String.format("Value of %s should start with 'migrated-auth-server-' prefix.", MIGRATED_AUTH_SERVER_CONTAINER_PROPERTY)); } // validate setup if (suiteContext.getMigratedAuthServerInfo() == null) { throw new RuntimeException(String.format("Migration test was enabled but no auth server from which to migrate was activated. " - + "A container matching '%s' needs to be enabled in arquillian.xml.", MIGRATED_AUTH_SERVER_CONTAINER)); + + "A container matching auth-server-jboss-migration needs to be enabled in arquillian.xml.")); } } @@ -178,7 +173,8 @@ public class AuthServerTestEnricher { public void stopMigratedContainer(@Observes(precedence = 1) StartSuiteContainers event) { if (suiteContext.isAuthServerMigrationEnabled()) { - containerController.get().stop(suiteContext.getAuthServerInfo().getQualifier()); + log.info("## STOP old container: " + suiteContext.getMigratedAuthServerInfo().getQualifier()); + stopContainerEvent.fire(new StopContainer(suiteContext.getMigratedAuthServerInfo().getArquillianContainer())); } } @@ -189,14 +185,6 @@ public class AuthServerTestEnricher { LogChecker.checkJBossServerLog(jbossHomePath); } } -// -// public void startAuthServerContainer(@Observes BeforeSuite event) { -// startContainerEvent.fire(new StartContainer(suiteContext.getAuthServerInfo().getArquillianContainer())); -// } -// -// public void stopAuthServerContainer(@Observes AfterSuite event) { -// containerController.get().stop(suiteContext.getAuthServerInfo().getQualifier()); -// } public void initializeTestContext(@Observes(precedence = 2) BeforeClass event) { TestContext testContext = new TestContext(suiteContext, event.getTestClass().getJavaClass()); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java index 929c23d2b7..d576b49a12 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/SuiteContext.java @@ -98,6 +98,9 @@ public final class SuiteContext { for (ContainerInfo bInfo : getAuthServerBackendsInfo()) { containers += "Backend: " + bInfo + "\n"; } + if (isAuthServerMigrationEnabled()) { + containers += "Migrated from: " + System.getProperty("migrated.auth.server.version") + "\n"; + } return "SUITE CONTEXT:\n" + containers; } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java index 333c5ddef9..78d26d5059 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/migration/MigrationTestExecutionDecider.java @@ -38,7 +38,7 @@ public class MigrationTestExecutionDecider implements TestExecutionDecider { if (migrationTest && migrationAnnotation != null) { String versionFrom = migrationAnnotation.versionFrom(); - if (migratedAuthServerVersion.equals(versionFrom)) { + if (versionFrom.equals(migratedAuthServerVersion)) { return ExecutionDecision.execute(); } else { return ExecutionDecision.dontExecute(method.getName() + "doesn't fit with migration version."); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java index 78d4faa2c7..1864324c78 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java @@ -17,69 +17,52 @@ package org.keycloak.testsuite.migration; import java.util.List; -import static org.junit.Assert.*; -import org.junit.Ignore; +import org.junit.After; +import org.junit.Before; import org.junit.Test; -import org.keycloak.admin.client.resource.ClientsResource; import org.keycloak.admin.client.resource.RealmResource; -import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.arquillian.migration.Migration; /** * @author Vlastislav Ramik */ public class MigrationTest extends AbstractKeycloakTest { - + + private RealmResource realmResource; + private RealmRepresentation realmRep; + @Override public void addTestRealms(List testRealms) { log.info("Adding no test realms for migration test. Test realm should be migrated from previous vesrion."); } - @Test - @Migration(versionFrom = "1.6.1.Final") - public void migration16Test() { - RealmResource realmResource = adminClient.realms().realm("Migration"); - RealmRepresentation realmRep = realmResource.toRepresentation(); - assertEquals("Migration", realmRep.getRealm()); - - List realmRoles = realmResource.roles().list(); - assertEquals(1, realmRoles.size()); - assertEquals("offline_access", realmRoles.get(0).getName()); - - for (ClientRepresentation client : realmResource.clients().findAll()) { - final String clientId = client.getClientId(); - switch (clientId) { - case "realm-management": - assertEquals(13, realmResource.clients().get(client.getId()).roles().list().size()); - break; - case "security-admin-console": - assertEquals(0, realmResource.clients().get(client.getId()).roles().list().size()); - break; - case "broker": - assertEquals(1, realmResource.clients().get(client.getId()).roles().list().size()); - break; - case "account": - assertEquals(2, realmResource.clients().get(client.getId()).roles().list().size()); - break; - default: - fail("Migrated realm contains unexpected client " + clientId); - break; - } - } + @Before + public void beforeMigrationTest() { + realmResource = adminClient.realms().realm("Migration"); + realmRep = realmResource.toRepresentation(); } @Test - @Migration(versionFrom = "1.5.1.Final") - @Ignore - public void migration15Test() { - for (RealmRepresentation realm : adminClient.realms().findAll()) { - System.out.println(realm.getRealm()); - } + @Migration(versionFrom = "1.9.8.Final") + public void migration198Test() { + Assert.assertNames(realmResource.roles().list(), "offline_access", "uma_authorization"); + Assert.assertNames(realmResource.clients().findAll(), "admin-cli", "realm-management", "security-admin-console", "broker", "account"); //TODO } - + + /** + * Assumed that there is only one migration test for each version and *remove* + * 'Migration' realm from Keycloak after test to be able to run the rest + * of the testsuite isolated afterward. + */ + @After + public void afterMigrationTest() { + log.info("removing '" + realmRep.getRealm() + "' realm"); + removeRealm(realmRep); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml index 215da8e498..8c996d603f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/arquillian.xml @@ -150,11 +150,11 @@ - + - + - ${auth.server.jboss.kc16} + ${auth.server.jboss.migration} org.jboss.as.arquillian.container.managed.ManagedDeployableContainer ${keycloak.migration.home} @@ -164,62 +164,10 @@ -Dkeycloak.migration.strategy=OVERWRITE_EXISTING -Dkeycloak.migration.realmName=Migration -Djboss.socket.binding.port-offset=${auth.server.port.offset} - -Xms64m -Xmx512m -XX:MaxPermSize=256m + ${auth.server.memory.settings} ${auth.server.management.port} - ${auth.server.startup.timeout} - - - - - - ${auth.server.jboss.kc15} - org.jboss.as.arquillian.container.managed.ManagedDeployableContainer - ${keycloak.migration.home} - - -Dkeycloak.migration.action=import - -Dkeycloak.migration.provider=singleFile - -Dkeycloak.migration.file=${keycloak.migration.file} - -Dkeycloak.migration.strategy=OVERWRITE_EXISTING - -Dkeycloak.migration.realmName=Migration - -Djboss.socket.binding.port-offset=${auth.server.port.offset} - -Xms64m -Xmx512m -XX:MaxPermSize=256m - - ${auth.server.management.port} - ${auth.server.startup.timeout} - - - - - - ${auth.server.jboss.kc14} - org.jboss.as.arquillian.container.managed.ManagedDeployableContainer - ${keycloak.migration.home} - -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m - ${auth.server.management.port} - ${auth.server.startup.timeout} - - - - - - ${auth.server.jboss.kc13} - org.jboss.as.arquillian.container.managed.ManagedDeployableContainer - ${keycloak.migration.home} - -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m - ${auth.server.management.port} - ${auth.server.startup.timeout} - - - - - - ${auth.server.jboss.kc12} - org.jboss.as.arquillian.container.managed.ManagedDeployableContainer - ${keycloak.migration.home} - -Djboss.socket.binding.port-offset=${auth.server.port.offset} -Xms64m -Xmx512m -XX:MaxPermSize=256m - ${auth.server.management.port} - ${auth.server.startup.timeout} + ${auth.server.jboss.startup.timeout} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json similarity index 57% rename from testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json rename to testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json index 3719d043f0..5b95d1cf26 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-16.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-1.9.8.Final.json @@ -4,6 +4,7 @@ "notBefore" : 0, "revokeRefreshToken" : false, "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, "ssoSessionIdleTimeout" : 1800, "ssoSessionMaxLifespan" : 36000, "offlineSessionIdleTimeout" : 2592000, @@ -25,13 +26,13 @@ "quickLoginCheckMilliSeconds" : 1000, "maxDeltaTimeSeconds" : 43200, "failureFactor" : 30, - "privateKey" : "MIIEowIBAAKCAQEAg/XlZqOYbYHyzHjWKwCD35JKloSyBBaIQgQbUjmWSBLw6xyNLSSvI45lmhoxcJTjxeZ+LKudfcoDVcLah1kYmTiS5YtNROeqmdWTkekdsAW8PYQJ0ScpatJ3jQ6xpe2E/AQWel5h6HI07O/r1mc3JDCXSe5zKdV9C0aGZpQSU2jWkVmP1cc2EZg5bVD1v057CUpKAX3qkloXpRedq6tMgwRSurhgnWDt93xWcv/+zz1Rw400batHmAm8Xa792jfZhSjvxtv9Q83Eb9jqi+c3BnxC3hucUDc6ivm8UgKYha054IOVbG8wDtmhZF8LlvLpjPk9iHuygS0zRnRuCo1+pQIDAQABAoIBACx5B8oSooFthS2CH/O4JbmIbRjTOceE7IELL0YD4HED6SvjoHSxY1EhYX6RC05871K3/pgBcn99QKh7lfh9f3vMBD3WN8FcLjPQNf67yOSU2j8FK+XQQ/YbXm0soZRhOytQGV8+RdL4AnxD04CboorQ0Xv6H9feelj9eLhDePWg5qEGHZJA6zGYiOUBALAL+SXoL59LWWLEM48TQWM0yGCA3mQM0iWCclbLNM1ls5gwMxSDdJeKC/3qlB6egGqPtXCEJdQXYqt3do8UUnxdQEkRGlJx14cSoH7fmZyZjLsEBcQT5uoAHI7/NMVN1DoFgwMTsq/MAATh3ngHqSl6J2ECgYEA3Ati3EaI0Vb8KzdjwZVgk9/KKnGLcswOl+cfU+lL9Vv5W4lVht3zvNAO8mUjtTSpjCF7LlY2lD9JEsv4cA9T3v2L7ZjkBiD6S/YYnEZYGAOjJb+LniRLqSVgN3beUgSiG/zzwuJm92J3dIcqMIPi4gdLMJ7KAv9qgbOddAy4b3kCgYEAmYXlqAu+Vgyjj3wzfgeXKzYkUfbEXUzsdugpW2gCvKi2/lJwmzORfsvSL88CxKZvzbEb1AJOrW+aPUbO0vPWo07ztphPKed+Gydp2G837f24eGZpBpxG6ATIa5MjtCHgX5guTMA3sJCFyLdMacRmlkmZEwbk7e9QxCp9HOmFoY0CgYB5u1LVya+nIBghUGM/lQP4yrVtBaO/vmPUZWhPY6FB+7/XhAJsuh09N10NfCZk+N1TSLJ8z/UhzmD+pRir5c7gbiQbLZn4SgYuP9cdnUze/CQlnfH/atTwnly8UmZruWR1V1sDVXzhHvg23w/YBx5dLOvL2gyn2+VwG43fxanDAQKBgFpFqWzOuvTOKb7NQVnyDKmUBHdNqtlRyhmBGhBtcG6OpkuVHjGkeQEdyFHkX7RSSZuhcMORN8IzxXYSlLrmNmeAnT3ZAXOac0R0QIDLpQ+ECVyCm28PpYH4jgDzXCMnaE/NpCvtOtHPqVsErSHkIo5saF4Px71A4zT15uuBRNphAoGBAMXV0eqeZOr/iHGIie3Ol0atZuB9b/BgLJBTFsCbFoVLdMAah4i2MXDm3vOUWlPf2VFL1LcKXYQ1GZ79We5LqG5w1CLA5WNt93U3yyl3/V5w02My5dVhz9BD4kWhZcvih+uVuHBxeI8Q8AvU63qqT4punQW4SSAHC+9e3U62aNc+", - "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg/XlZqOYbYHyzHjWKwCD35JKloSyBBaIQgQbUjmWSBLw6xyNLSSvI45lmhoxcJTjxeZ+LKudfcoDVcLah1kYmTiS5YtNROeqmdWTkekdsAW8PYQJ0ScpatJ3jQ6xpe2E/AQWel5h6HI07O/r1mc3JDCXSe5zKdV9C0aGZpQSU2jWkVmP1cc2EZg5bVD1v057CUpKAX3qkloXpRedq6tMgwRSurhgnWDt93xWcv/+zz1Rw400batHmAm8Xa792jfZhSjvxtv9Q83Eb9jqi+c3BnxC3hucUDc6ivm8UgKYha054IOVbG8wDtmhZF8LlvLpjPk9iHuygS0zRnRuCo1+pQIDAQAB", - "certificate" : "MIICoTCCAYkCBgFQs9TiPDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTUxMDI5MTMzOTQ0WhcNMjUxMDI5MTM0MTI0WjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCD9eVmo5htgfLMeNYrAIPfkkqWhLIEFohCBBtSOZZIEvDrHI0tJK8jjmWaGjFwlOPF5n4sq519ygNVwtqHWRiZOJLli01E56qZ1ZOR6R2wBbw9hAnRJylq0neNDrGl7YT8BBZ6XmHocjTs7+vWZzckMJdJ7nMp1X0LRoZmlBJTaNaRWY/VxzYRmDltUPW/TnsJSkoBfeqSWhelF52rq0yDBFK6uGCdYO33fFZy//7PPVHDjTRtq0eYCbxdrv3aN9mFKO/G2/1DzcRv2OqL5zcGfELeG5xQNzqK+bxSApiFrTngg5VsbzAO2aFkXwuW8umM+T2Ie7KBLTNGdG4KjX6lAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAIOonQw/DIwSDU2oFyoaZ7VfFcT6Uh3QXfLB4ZYDek4m6Y8onrtqzuGo2Z2tcokV9PPkBAQtefSxIqx4COC9OeDBy1DeY04AkpprywLuOzCL/OBCV9QwiMkuBYYC5JJCIyTuo3qYxkuDA/iwDdH7vNDGohE6TDYmrC3Yp3lsLaHZ1lG52Df3qL8wo3/PdwzCOmyRGfm3vscTY+PqVCYcfxg9nPkoU3FwUJDAshjMxRw7/aafG8OmQ2EeYH4HV/FsULFrwoVF0Up8TkqAhmdJJ+vaGqaHHXQyiIPTMCdrkE58jAvtpbX0XCEDiNXT9qORhmjKP8Xq13XOQTfI5DRo8Qc=", - "codeSecret" : "56227bc5-edb9-419c-a019-d61a7c6ffb74", + "privateKey" : "MIIEogIBAAKCAQEAvsWl692vnWYMFpHgMbQ6NnSMLaTMzFg5EEe3qHsm8jqh6GYIwgGvxUNYwZKKimw4D2f3+0PEHd+bDBa3Qb2WjOibUXcxgPTxXOP7awJATEeTiozxLan13Q0RSF7P7uHhyBNNGnMmQouGv3gi97SryPp0XzIY5Na3ZvHxbInyYfGE920GD+FrauV3i75588bFngCSCUcOdOuVifKkChsOhfpvxlvCzY0pP8hqMzBCqFim+KJI+6il89fBdd2uD1umwOu/vhLK28q2O+jNhKxt37jFJEhWR9D0wdilWOK7O09oCpb2g57H+4/uOd8ROOT+7cZjk+COgs2m7jMNTQniKwIDAQABAoIBAC0ymJ/sMtpAviZuw0YjZBNYw4lg3SCg2kwwIaQqIU0MdSWhXvvP+Hvs+89Iz8gB4bpMsKT4JjPJsWtIynwp1cxmo73GVpUt8y1F2mA89UzdPGh8AfwGo3iOywRxaiQ3og2L/R2CIJNtiSw941L6nCSOqhmPvfQhpuwd8Ev91s47R/FppdyUA6GG7//JepK5RfbHdSD7uRjebRE92fvAhS1jhE3AtvrmN/OZMSY7saGRMK6Ht7mFu1QKWhCpCT6EyXrJhpidIdchgf/VJOA9+wAPfQdHtAZpOstDzTo59QMYcyOTu4AaaIV03oA6Cw/1MS8kSBqFSG5w8Sgaal74RkECgYEA+o9FWybpqLnI4IbGr1yixnYlF9l5RpHbiwnNOeYPHHi4qXXm1LOzuB9nAiJuhBvHFQX4C/1a7sjQZapgHzi0WE/ZgK97PIrVAaXs+CJM1u9TnOBUwHMd+X88PNs+Ee4gC3W9mLF2rVZ8+0C7qm4kTueqxMbD7+RPPDp+Qyy/xAsCgYEAwuoMrFc4nlg8ND91Fvkk8Q7fOLEEohJSzLHbZh/ZvhXAxAjaBM985olN0tju2yq8PER4C9+Ys+QOQ9rfHZPL7Ms1NbTSkseuDIpkBbPXDaoJCmkaILgKMk2yeghuYX97AIPrK+7HwgK/wR0a1v0iTqTKoU+BHHSgSd/y8HP4DmECgYAPsq47ucJl6c3mE0ubbQhdusU0K1Hn0/eXTHPpLrq/o+id5V8i8GdQH9eKULHv4PWmIYua5LKvxzEXTYdcLWdPKSGNaHEmJ+SUh9rC6RM7XB828u8cZ5n3KbuSbIIFCZBEZ4oLaQLwdTy7WSDo2qG3t9gBIBriisFUGq4Sc1lIZQKBgBKlCtNNgJf8/r1MuKma6YK5lna40CWktRDpjoAlWdHKCOd3pUtCgcMXrT8XCzohy2HEdutD1zqV/RtWi3Mr2Rzsj0l045Ow0CBY+JpnCpI/CqBZT2uDz03iiskLl2tyI1T4SX2pWKhhVPBnFVMtYaO+NbfagBI1wcNvTbDclwThAoGAGMvfSlEsEk38t6XxzJFGiUyQvP5rVDVBOs7xQ+kRk/fH/Z6s9uWQuotMKcrbvy7gT/6BwLZgzfedB4qqgdl0TK45wi9hUUZ6IWyU1grVtkkCIxVr0XUm+u0dDUZ97gucV+qjrFwEqzY1GGQXTmX5qqPryGnp8GOqRMOyjkOjhTc=", + "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvsWl692vnWYMFpHgMbQ6NnSMLaTMzFg5EEe3qHsm8jqh6GYIwgGvxUNYwZKKimw4D2f3+0PEHd+bDBa3Qb2WjOibUXcxgPTxXOP7awJATEeTiozxLan13Q0RSF7P7uHhyBNNGnMmQouGv3gi97SryPp0XzIY5Na3ZvHxbInyYfGE920GD+FrauV3i75588bFngCSCUcOdOuVifKkChsOhfpvxlvCzY0pP8hqMzBCqFim+KJI+6il89fBdd2uD1umwOu/vhLK28q2O+jNhKxt37jFJEhWR9D0wdilWOK7O09oCpb2g57H+4/uOd8ROOT+7cZjk+COgs2m7jMNTQniKwIDAQAB", + "certificate" : "MIICoTCCAYkCBgFW6dMbGDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTYwOTAyMDczNDAxWhcNMjYwOTAyMDczNTQxWjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+xaXr3a+dZgwWkeAxtDo2dIwtpMzMWDkQR7eoeybyOqHoZgjCAa/FQ1jBkoqKbDgPZ/f7Q8Qd35sMFrdBvZaM6JtRdzGA9PFc4/trAkBMR5OKjPEtqfXdDRFIXs/u4eHIE00acyZCi4a/eCL3tKvI+nRfMhjk1rdm8fFsifJh8YT3bQYP4Wtq5XeLvnnzxsWeAJIJRw5065WJ8qQKGw6F+m/GW8LNjSk/yGozMEKoWKb4okj7qKXz18F13a4PW6bA67++EsrbyrY76M2ErG3fuMUkSFZH0PTB2KVY4rs7T2gKlvaDnsf7j+453xE45P7txmOT4I6CzabuMw1NCeIrAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAI+arTjuHHyLyu/D8KGa+yzPc7RlaOimuf53e3Rtih2sd9W3cid6laRIr23w39of4msYOo9UE5CJAzYhmfUvqV2jYs/veBpwYcrLk3VNxkXNvEXpgJZ/Qa8tAzfejul52+T3iced8b4iIVx4X1H7+BiBCWQzAWE+lC0F+23fPuaT7EgoR4eXSwjDTLcEihs/vfepMX+TAsEmmu8ZCsameeYrrMoT1mfPP7uyKcZDDFnujJFFekXEp/9hOkW7S1LR2/XAp5v7KwB4hdIG+ajBg4cO4ZkqpjkAwt2vgnV2QjhyE5ZoOiIr6hYvsBIBoRXkN1x/w6Rbd9X0qA2i+IUApe4=", + "codeSecret" : "fac9fa12-39d6-4dd1-bbe0-24e3d0a65af8", "roles" : { "realm" : [ { - "id" : "c4aae789-de76-4130-a06b-a28113ada698", + "id" : "d98dc6ae-7f98-4066-bce4-d8bbc38ee322", "name" : "offline_access", "description" : "${role_offline-access}", "scopeParamRequired" : true, @@ -39,114 +40,117 @@ } ], "client" : { "realm-management" : [ { - "id" : "22345bd8-afee-44c3-9958-a134e729aaa7", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", + "id" : "5d6ab47a-9ce2-4837-9744-3ea972d44acc", + "name" : "view-clients", + "description" : "${role_view-clients}", "scopeParamRequired" : false, "composite" : false }, { - "id" : "6c6bb910-a769-4e92-b009-db4b9ab32c67", - "name" : "manage-events", - "description" : "${role_manage-events}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "5327bf34-5a16-4f36-bb15-100a25aac33e", - "name" : "view-realm", - "description" : "${role_view-realm}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "3c52d428-e3e5-40b3-92d4-ab6195b7dce5", - "name" : "create-client", - "description" : "${role_create-client}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "9999e081-5321-4c19-a8ac-27cea3bbde3a", - "name" : "impersonation", - "description" : "${role_impersonation}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "7c857cf1-b66e-4935-8749-580062d4719a", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "549d2e65-d347-4221-bde0-65fff6580fc2", - "name" : "view-events", - "description" : "${role_view-events}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "aa1676b8-a92a-4c99-b266-54858129942d", - "name" : "view-users", - "description" : "${role_view-users}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "6c9a78fa-0e37-48bf-a9b5-2062312b0f33", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "d38072d6-66fe-4102-8d4d-b5e8e2721e43", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "a85da016-830e-42dd-8318-3cc8c28d3382", - "name" : "manage-users", - "description" : "${role_manage-users}", - "scopeParamRequired" : false, - "composite" : false - }, { - "id" : "0ab22444-1235-4391-ac10-571b33065177", + "id" : "dc211380-6293-4d64-a638-893a26f8d97c", "name" : "realm-admin", "description" : "${role_realm-admin}", "scopeParamRequired" : false, "composite" : true, "composites" : { "client" : { - "realm-management" : [ "view-identity-providers", "manage-clients", "manage-events", "view-realm", "manage-realm", "manage-users", "create-client", "impersonation", "view-events", "manage-identity-providers", "view-clients", "view-users" ] + "realm-management" : [ "view-clients", "view-events", "view-realm", "manage-events", "manage-identity-providers", "manage-users", "manage-realm", "create-client", "manage-clients", "impersonation", "view-users", "view-identity-providers" ] } } }, { - "id" : "442fcc9e-46af-495a-9cdf-64d32dabc808", - "name" : "view-clients", - "description" : "${role_view-clients}", + "id" : "371c927a-7616-4394-a5aa-ba43d9d172dd", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "6b6538b4-7432-49d3-ac9b-49d934552efb", + "name" : "impersonation", + "description" : "${role_impersonation}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "727bf71c-07c7-4601-8f88-872cc3da3ae3", + "name" : "view-events", + "description" : "${role_view-events}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "2d270965-1e4e-44e0-90b8-bbf0e3dfff85", + "name" : "view-realm", + "description" : "${role_view-realm}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "99f852cd-f534-4320-b8c3-2252667cfbff", + "name" : "manage-events", + "description" : "${role_manage-events}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "f2fcb9cd-66c3-4118-a72a-a8a630a8e85c", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "35a32ad2-84d3-48e8-9d4d-fec93a1ea355", + "name" : "manage-users", + "description" : "${role_manage-users}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "84a16c8b-2225-4c2e-a36b-10b73e57fb49", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "7e74c0ad-c222-4a98-a2fb-ce5f3cd75168", + "name" : "create-client", + "description" : "${role_create-client}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "6e4d912f-6abd-4fff-9f69-0e403492e61e", + "name" : "view-users", + "description" : "${role_view-users}", + "scopeParamRequired" : false, + "composite" : false + }, { + "id" : "ff1f784d-00e7-46ab-801b-d45923e52b60", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", "scopeParamRequired" : false, "composite" : false } ], "security-admin-console" : [ ], + "admin-cli" : [ ], "broker" : [ { - "id" : "8d46836e-eb6c-4cf5-97fe-8b1b24a69e10", + "id" : "9e61a106-0c51-4f68-9f7c-f2233a07b8b9", "name" : "read-token", "description" : "${role_read-token}", "scopeParamRequired" : false, "composite" : false } ], "account" : [ { - "id" : "40799d46-6574-4d45-a157-33cc15e3e2f1", - "name" : "manage-account", - "description" : "${role_manage-account}", + "id" : "42b51a97-a178-4232-810d-c691f0efc978", + "name" : "view-profile", + "description" : "${role_view-profile}", "scopeParamRequired" : false, "composite" : false }, { - "id" : "d6056197-e9a3-4922-8b1b-ce6e99a71a43", - "name" : "view-profile", - "description" : "${role_view-profile}", + "id" : "4f2e7b5c-8ec4-4a13-abe8-5bd25f0890ac", + "name" : "manage-account", + "description" : "${role_manage-account}", "scopeParamRequired" : false, "composite" : false } ] } }, + "groups" : [ ], "defaultRoles" : [ "offline_access" ], "requiredCredentials" : [ "password" ], + "passwordPolicy" : "hashIterations(20000)", "otpPolicyType" : "totp", "otpPolicyAlgorithm" : "HmacSHA1", "otpPolicyInitialCounter" : 0, @@ -155,46 +159,39 @@ "otpPolicyPeriod" : 30, "clientScopeMappings" : { "realm-management" : [ { + "client" : "admin-cli", + "roles" : [ "realm-admin" ] + }, { "client" : "security-admin-console", "roles" : [ "realm-admin" ] } ] }, "clients" : [ { - "id" : "cdf6e789-79b9-41ad-b4a3-f02abd2aeab6", - "clientId" : "realm-management", - "name" : "${client_realm-management}", + "id" : "7e82c993-ea0a-4ec2-813e-2744e5f9f821", + "clientId" : "account", + "name" : "${client_account}", + "baseUrl" : "/auth/realms/Migration/account", "surrogateAuthRequired" : false, "enabled" : true, "clientAuthenticatorType" : "client-secret", - "secret" : "c51e802e-e33b-431e-8e74-c2ebd4ba6abf", - "redirectUris" : [ ], + "secret" : "d42d8648-e5c0-431f-8f3e-cb0e790e6cdf", + "defaultRoles" : [ "view-profile", "manage-account" ], + "redirectUris" : [ "/auth/realms/Migration/account/*" ], "webOrigins" : [ ], "notBefore" : 0, - "bearerOnly" : true, + "bearerOnly" : false, "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, "publicClient" : false, "frontchannelLogout" : false, "attributes" : { }, "fullScopeAllowed" : false, "nodeReRegistrationTimeout" : 0, "protocolMappers" : [ { - "id" : "cfaff5c8-a0e3-42af-8dcd-f7ae6000a240", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - }, { - "id" : "5a68a544-0373-4cf3-9978-aed944df478f", + "id" : "a284d825-5de2-400e-b4a7-f172b5d58eb3", "name" : "full name", "protocol" : "openid-connect", "protocolMapper" : "oidc-full-name-mapper", @@ -205,7 +202,7 @@ "access.token.claim" : "true" } }, { - "id" : "41c006db-88d6-42a6-addd-8efb535f1a7d", + "id" : "5c6e3180-55e4-4756-a480-042ca86bc2ca", "name" : "role list", "protocol" : "saml", "protocolMapper" : "saml-role-list-mapper", @@ -216,21 +213,7 @@ "attribute.name" : "Role" } }, { - "id" : "d6fd0e72-aa1e-417d-b28b-ec31946dc6fd", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "d8692a7a-366d-407d-abc1-a6f45742c47c", + "id" : "5d24d4f3-5803-4c5e-8af4-280db49021cc", "name" : "given name", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", @@ -244,7 +227,7 @@ "jsonType.label" : "String" } }, { - "id" : "54f4844b-aaa5-4260-b2aa-5dc446c8b978", + "id" : "4c6f6718-bdec-47f5-9992-f1d23462c7d5", "name" : "family name", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", @@ -257,30 +240,414 @@ "claim.name" : "family_name", "jsonType.label" : "String" } - } ] + }, { + "id" : "4e7fba71-906e-4f3c-be7f-679ef09fb968", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${username}", + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "80727ad9-91de-49ce-8087-448c611f8af4", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${email}", + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false }, { - "id" : "7776fa56-ab87-4638-b42b-cc9537ab2fc2", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "baseUrl" : "/auth/admin/Migration/console/index.html", + "id" : "4451c2d8-3467-46cd-8d9f-f1b48bcde4c1", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", "surrogateAuthRequired" : false, "enabled" : true, "clientAuthenticatorType" : "client-secret", - "secret" : "5e0673fa-921d-4415-9d92-3a4197d87e46", - "redirectUris" : [ "/auth/admin/Migration/console/*" ], + "secret" : "c3b62964-9f68-4c69-998b-1ee5c208515f", + "redirectUris" : [ ], "webOrigins" : [ ], "notBefore" : 0, "bearerOnly" : false, "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, "publicClient" : true, "frontchannelLogout" : false, "attributes" : { }, "fullScopeAllowed" : false, "nodeReRegistrationTimeout" : 0, "protocolMappers" : [ { - "id" : "9ed45252-c571-44fe-ac5f-b30cea378ff1", + "id" : "070e3acb-4d25-4f8f-9e37-9fc8e8696942", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${username}", + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "7529b783-69e0-4741-ac69-ea8f886d134b", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "ee3c9db0-1dc9-49e3-baff-7aa1b0f79b22", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${email}", + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "b9a2ed1b-a2a1-4b86-a517-cbe23d59f599", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${familyName}", + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "8548e7d4-1191-4ebe-acb4-0caba2850516", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : true, + "consentText" : "${fullName}", + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "816ff5d5-d594-4daf-94c3-8e40762b4d82", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${givenName}", + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false + }, { + "id" : "a3b3fe5c-6370-492e-8656-b42a9e2c1e56", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "2e474414-43ed-44ac-a339-afaa06ca3c8d", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "ed579581-fa9f-469c-a1f8-75a12d7b4533", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${familyName}", + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "531ca32b-51fe-4fa9-ad6c-ede79b85f2a9", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${givenName}", + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "821eeec5-c716-4f42-bc18-d4630d011ae1", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${email}", + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + }, { + "id" : "c9460f3a-fb5c-43b1-a3eb-cbf881b887f9", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${username}", + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "fab1384f-5cfd-4543-8bab-952c44cee2f5", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "e1128ee4-c9fc-4320-9a69-baa572a029ef", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : true, + "consentText" : "${fullName}", + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false + }, { + "id" : "82814fb8-1db4-40a1-823b-f17e8dbed34e", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "fdac74b0-5621-4301-930a-5d4e04727987", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "attributes" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "25df2680-733a-4cea-b4ae-b0210e532a48", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${username}", + "config" : { + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "e686a4ab-733d-4f7a-8198-dbb9818a4fba", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${familyName}", + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "1351a931-6fa3-43cc-8e11-f281ddd7d89f", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : true, + "consentText" : "${fullName}", + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true" + } + }, { + "id" : "56df66c3-d88c-47d0-b9fd-b5bfa678a3e6", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${givenName}", + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "99ec226f-003e-482b-9e6b-d607fcdadd0f", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "90f38a5b-eef6-4d92-8ee8-2f9430ca5657", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${email}", + "config" : { + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false + }, { + "id" : "e025cdec-60bc-4959-8853-25c4bab219a5", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "baseUrl" : "/auth/admin/Migration/console/index.html", + "surrogateAuthRequired" : false, + "enabled" : true, + "clientAuthenticatorType" : "client-secret", + "secret" : "a5247258-4222-4571-8d95-22cb2e85be9f", + "redirectUris" : [ "/auth/admin/Migration/console/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "attributes" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "f9bd8e5a-8433-4070-b3c7-f7c35f89717e", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${givenName}", + "config" : { + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "60569fde-f909-4ae1-9560-e4fb39332114", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : true, + "consentText" : "${familyName}", + "config" : { + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "5acae5e3-c798-4f4c-8e63-c69cc15f918f", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + }, { + "id" : "7521f6e3-9246-40bb-8879-3ecb580da104", "name" : "locale", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-attribute-mapper", @@ -294,32 +661,7 @@ "jsonType.label" : "String" } }, { - "id" : "d1b5694e-e9e2-4d56-9019-bc658cdcded8", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - }, { - "id" : "497fee7a-23b4-4345-a872-63444a8b1a27", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "57881d46-deca-421e-a4c5-e023e747f68e", + "id" : "7e84d6ca-4258-494f-9e9f-5cee8b8a0b45", "name" : "username", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", @@ -333,168 +675,7 @@ "jsonType.label" : "String" } }, { - "id" : "9cfe7043-ba2b-49e2-8a1b-f1b23fcb5eb5", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "c37a3c4a-8999-4111-ae2a-98954a5a8674", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "a7917c74-f18a-43a0-a787-7afc7b45a247", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "e7faae41-f5e8-4571-b280-5bbe0d5bcb12", - "clientId" : "broker", - "name" : "${client_broker}", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "b2a1f1ff-5157-4240-9354-69a6deb13ccb", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "b843f1fd-da0a-4d49-b367-3fb39f11383b", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "3b18c534-1e0a-474c-adf8-e9fbc33c05e8", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "b7e9db64-52f6-4aba-9437-deefab06abee", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "2da52efa-e9d9-4b68-a296-0310059b7df2", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "2d2df25d-26d1-4e7c-a85a-c485ab2cc0fe", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "d096910d-13ac-43a7-bad8-4d1bbfd34171", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "a2864762-7cc1-4784-a540-439e611f29ba", - "clientId" : "account", - "name" : "${client_account}", - "baseUrl" : "/auth/realms/Migration/account", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "68cbd7a8-3b48-4751-a396-df7ab39a2fdf", - "defaultRoles" : [ "view-profile", "manage-account" ], - "redirectUris" : [ "/auth/realms/Migration/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "ed2c87d0-299a-40ac-a11c-df7af41bb365", + "id" : "80d84a82-5e9e-4142-bcab-0a8b752f9f07", "name" : "email", "protocol" : "openid-connect", "protocolMapper" : "oidc-usermodel-property-mapper", @@ -508,60 +689,7 @@ "jsonType.label" : "String" } }, { - "id" : "80bc8d1f-3cb8-4362-890c-68d1a5c7263d", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "af93478f-176d-4be4-be5d-78a65dd88717", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "b6c1704d-39fc-4b63-8f70-74561849654f", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "928dbc26-41a1-4342-ba92-c230a85e830c", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "03a967ab-ed2b-402f-ae2f-10729084376c", + "id" : "c8884632-dbfd-4b14-971c-c8fc5b08b20a", "name" : "full name", "protocol" : "openid-connect", "protocolMapper" : "oidc-full-name-mapper", @@ -571,11 +699,16 @@ "id.token.claim" : "true", "access.token.claim" : "true" } - } ] + } ], + "useTemplateConfig" : false, + "useTemplateScope" : false, + "useTemplateMappers" : false } ], + "clientTemplates" : [ ], "browserSecurityHeaders" : { - "contentSecurityPolicy" : "frame-src 'self'", - "xFrameOptions" : "SAMEORIGIN" + "xContentTypeOptions" : "nosniff", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'" }, "smtpServer" : { }, "eventsEnabled" : false, @@ -583,130 +716,56 @@ "enabledEventTypes" : [ ], "adminEventsEnabled" : false, "adminEventsDetailsEnabled" : false, - "identityFederationEnabled" : false, "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "alias" : "reset credentials", - "description" : "Reset credentials for a user if they forgot their password or something", + "id" : "a377c376-33e1-49d9-a395-0ec67437769c", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "reset-credentials-choose-user", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "reset-credential-email", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "reset-password", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 30 - }, { - "authenticator" : "reset-otp", - "autheticatorFlow" : false, - "requirement" : "OPTIONAL", - "userSetupAllowed" : false, - "priority" : 40 - } ] - }, { - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "client-jwt", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 20 - } ] - }, { - "alias" : "registration form", - "description" : "registration form", - "providerId" : "form-flow", "topLevel" : false, "builtIn" : true, "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "autheticatorFlow" : false, + "authenticator" : "idp-confirm-link", "requirement" : "REQUIRED", + "priority" : 10, "userSetupAllowed" : false, - "priority" : 20 + "autheticatorFlow" : false }, { - "authenticator" : "registration-profile-action", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", + "authenticator" : "idp-email-verification", + "requirement" : "ALTERNATIVE", + "priority" : 20, "userSetupAllowed" : false, - "priority" : 40 + "autheticatorFlow" : false }, { - "authenticator" : "registration-password-action", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "Verify Existing Account by Re-authentication", "userSetupAllowed" : false, - "priority" : 50 - }, { - "authenticator" : "registration-recaptcha-action", - "autheticatorFlow" : false, - "requirement" : "DISABLED", - "userSetupAllowed" : false, - "priority" : 60 + "autheticatorFlow" : true } ] }, { - "alias" : "direct grant", - "description" : "OpenID Connect Resource Owner Grant", + "id" : "20facc1e-c957-4835-ad3a-3a4fe887293c", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", "providerId" : "basic-flow", - "topLevel" : true, + "topLevel" : false, "builtIn" : true, "authenticationExecutions" : [ { - "authenticator" : "direct-grant-validate-username", - "autheticatorFlow" : false, + "authenticator" : "idp-username-password-form", "requirement" : "REQUIRED", + "priority" : 10, "userSetupAllowed" : false, - "priority" : 10 + "autheticatorFlow" : false }, { - "authenticator" : "direct-grant-validate-password", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "direct-grant-validate-otp", - "autheticatorFlow" : false, + "authenticator" : "auth-otp-form", "requirement" : "OPTIONAL", + "priority" : 20, "userSetupAllowed" : false, - "priority" : 30 - } ] - }, { - "alias" : "registration", - "description" : "registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "flowAlias" : "registration form", - "autheticatorFlow" : true, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 + "autheticatorFlow" : false } ] }, { + "id" : "eab28028-9b70-4226-bfe5-d107e3e2f8d8", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -714,24 +773,99 @@ "builtIn" : true, "authenticationExecutions" : [ { "authenticator" : "auth-cookie", - "autheticatorFlow" : false, "requirement" : "ALTERNATIVE", + "priority" : 10, "userSetupAllowed" : false, - "priority" : 10 + "autheticatorFlow" : false }, { "authenticator" : "auth-spnego", - "autheticatorFlow" : false, "requirement" : "DISABLED", + "priority" : 20, "userSetupAllowed" : false, - "priority" : 20 + "autheticatorFlow" : false }, { - "flowAlias" : "forms", - "autheticatorFlow" : true, "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "forms", "userSetupAllowed" : false, - "priority" : 30 + "autheticatorFlow" : true } ] }, { + "id" : "c0f484bf-1367-4319-b0a1-f8ff74822eb7", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "requirement" : "ALTERNATIVE", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "client-jwt", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "eed6b9c1-5a3a-4ec1-acec-549da4ba1be4", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-password", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "requirement" : "OPTIONAL", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "51b007c2-f394-44ae-af20-b9875d40fc15", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "requirement" : "ALTERNATIVE", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "requirement" : "ALTERNATIVE", + "priority" : 30, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "da947d83-d94f-47bc-a849-387d51eda02e", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -739,27 +873,125 @@ "builtIn" : true, "authenticationExecutions" : [ { "authenticator" : "auth-username-password-form", - "autheticatorFlow" : false, "requirement" : "REQUIRED", + "priority" : 10, "userSetupAllowed" : false, - "priority" : 10 + "autheticatorFlow" : false }, { "authenticator" : "auth-otp-form", - "autheticatorFlow" : false, "requirement" : "OPTIONAL", + "priority" : 20, "userSetupAllowed" : false, - "priority" : 20 + "autheticatorFlow" : false + } ] + }, { + "id" : "1f5adf60-276b-4ae1-bb87-9ffdb419f44a", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "requirement" : "REQUIRED", + "priority" : 10, + "flowAlias" : "registration form", + "userSetupAllowed" : false, + "autheticatorFlow" : true + } ] + }, { + "id" : "84ae147c-bb2d-48b3-b73e-b606e9c24795", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-profile-action", + "requirement" : "REQUIRED", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-password-action", + "requirement" : "REQUIRED", + "priority" : 50, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "registration-recaptcha-action", + "requirement" : "DISABLED", + "priority" : 60, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "d2851906-562c-4b4a-8c93-a5164f3800db", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-credential-email", + "requirement" : "REQUIRED", + "priority" : 20, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-password", + "requirement" : "REQUIRED", + "priority" : 30, + "userSetupAllowed" : false, + "autheticatorFlow" : false + }, { + "authenticator" : "reset-otp", + "requirement" : "OPTIONAL", + "priority" : 40, + "userSetupAllowed" : false, + "autheticatorFlow" : false + } ] + }, { + "id" : "1620a020-4499-437b-a6ee-9189d8aced3e", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "requirement" : "REQUIRED", + "priority" : 10, + "userSetupAllowed" : false, + "autheticatorFlow" : false } ] } ], - "authenticatorConfig" : [ ], - "requiredActions" : [ { - "alias" : "terms_and_conditions", - "name" : "Terms and Conditions", - "providerId" : "terms_and_conditions", - "enabled" : false, - "defaultAction" : false, - "config" : { } + "authenticatorConfig" : [ { + "id" : "a4d4a0ef-28f1-45ba-af2b-ecdcec16f0db", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" + } }, { + "id" : "904be702-e5e3-40f3-854d-6f598fea530b", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" + } + } ], + "requiredActions" : [ { "alias" : "CONFIGURE_TOTP", "name" : "Configure Totp", "providerId" : "CONFIGURE_TOTP", @@ -787,10 +1019,18 @@ "enabled" : true, "defaultAction" : false, "config" : { } + }, { + "alias" : "terms_and_conditions", + "name" : "Terms and Conditions", + "providerId" : "terms_and_conditions", + "enabled" : false, + "defaultAction" : false, + "config" : { } } ], "browserFlow" : "browser", "registrationFlow" : "registration", "directGrantFlow" : "direct grant", "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients" + "clientAuthenticationFlow" : "clients", + "keycloakVersion" : "7.0.0.GA" } \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json b/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json deleted file mode 100644 index 86e4606f94..0000000000 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/migration-test/migration-realm-15.json +++ /dev/null @@ -1,751 +0,0 @@ -{ - "id" : "9c3a9824-cc8b-46f6-8922-cd576a92850f", - "realm" : "Migration", - "notBefore" : 0, - "accessTokenLifespan" : 300, - "ssoSessionIdleTimeout" : 1800, - "ssoSessionMaxLifespan" : 36000, - "accessCodeLifespan" : 60, - "accessCodeLifespanUserAction" : 300, - "accessCodeLifespanLogin" : 1800, - "enabled" : true, - "sslRequired" : "external", - "registrationAllowed" : false, - "registrationEmailAsUsername" : false, - "rememberMe" : false, - "verifyEmail" : false, - "resetPasswordAllowed" : false, - "editUsernameAllowed" : false, - "bruteForceProtected" : false, - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "privateKey" : "MIIEpAIBAAKCAQEA29+/bYOEg+RFlDgKjX0nv+UMkV8X06E1XvRobuQjXKOV613VJIa1F/nGabXthkM3tC7DadJ5y1tBwhF+bJzMA4w38zNfJdjEp3DRND6ypUn0SJZrSw6l3u3w+s5uemgTWUZk463Xr3HbDxtnG+4t5GuHA2Oq6O2OLniVZKbDTpgF1HxzCBQiAxi2jNJm3tMlTdN6D/nV3Rwp2T1250T3ldkM3TDK/Nlup3oOejy+qRGEmh+omuABOOJ8icCULZ5S2AbiqfojP5ZN3WEpyCqcQvsdop4IawUbTDyy9BCE2K5CCZ6ZgQaSnpJZGUy91crPJXnI4tlg5Mh88l8aSrBLsQIDAQABAoIBAA5J7SPNzzfPBuKJ/c2SG5ox5W4xEthS+qfwFDVYqB+mFeEU2PwlsPEc71MBWq1GAwG3pEVlQzr+9DgLcP7X9b4pR52LchyAiM8k2sOda3ioZLKu68wV6JujNOznq3BTASblFztgmcqyCH1j14COKvdUMZL70CiQ/5NvjK3c1IZv5d/S9B7Qhd2o/6cO51xIodE87Lc4Pghq8cQ/AJJUJokyFtjkCpTNAYxcZgyiEMNbyjrbNMMEpiuspZ50eRbi7SOKOg6mSjwuTeK0cQ57JDuMhE/iyaMwh98uqSTccqeKS672z+7QCu89ce1YZMnWtjfwKEiIcTWB71pvy2gGwgECgYEA9Afv+5Zop4j1kmZvQcdr+UpW3Ia91nNelvlkMYPMrsC24xwrGhO9Hx76VxdBFCzDuYBIyOzbPLV7kFojSKmcWB6hb/S/j6eMd46ZetycrfH5sRpJHmqJpGZiARrWTLsFRNDwi2jwEl2qt3wkq/IBvuzNt9bwbnsajgRVSVWUBtECgYEA5qh+hl1f8R6KcrUro9kSSxjmlqzSIeAYNJ0VJkr2ZjRd984xTRSnUcuVJnbfNgfmywCB9s7QGgcMrs9BejKuP1bq2hnjjA1WOvz0Dq1FRw3wqYSZWHtVO2h/QDaKIcGjQ/PyWAyrOTBaL+bzKrNO66L7CQK63A4/Gj7QivFA7uECgYEAmYW81pyDbpLdW6MR72IUbZr1Fnu2RooCQhzXiccPKAmZhTudaiRs4H1OpSe+C4E2CSfJoo5QRtstx1zNwdLixxVOHu7s7OVNm5GcwQy1jUEkAuU0huwjd8fpdCR8GX23DNod2rbEAennktOJBpuTuZekvDl+vSK5TAsx1JcAL2ECgYB7rPTKjt6Wps2NW98eZ5ILejqJp/iz+TiBXYitk5wyiPmpmYGN1vkwPnymty5QBkSVrJwC/jlO+2CtiquNHgeYJr6eWytLOQt3bZJfHED9LFhSTKr8aoT06b7xa0z9dJpaIT9cPs7AR1DURn0z9Bjo9+aqmjAfNfRX2j5vgZRTgQKBgQC7+9bt4yZ0MAxJYTMVqU/LnyjPuDrgXZJYw5ZYO6r5xF0mdovE9+lY6I8OeAUg428Zk8mxMYeqOFUHF8nVBxofHrZbXR2eJxJLRO8f2GPRFYanA9MNe1Jc0WV5bi1gF+ifC0j//W1kGxCHJX1OeMSV/h8r3OaIHEwuu30ZLHFxRg==", - "publicKey" : "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA29+/bYOEg+RFlDgKjX0nv+UMkV8X06E1XvRobuQjXKOV613VJIa1F/nGabXthkM3tC7DadJ5y1tBwhF+bJzMA4w38zNfJdjEp3DRND6ypUn0SJZrSw6l3u3w+s5uemgTWUZk463Xr3HbDxtnG+4t5GuHA2Oq6O2OLniVZKbDTpgF1HxzCBQiAxi2jNJm3tMlTdN6D/nV3Rwp2T1250T3ldkM3TDK/Nlup3oOejy+qRGEmh+omuABOOJ8icCULZ5S2AbiqfojP5ZN3WEpyCqcQvsdop4IawUbTDyy9BCE2K5CCZ6ZgQaSnpJZGUy91crPJXnI4tlg5Mh88l8aSrBLsQIDAQAB", - "certificate" : "MIICoTCCAYkCBgFQs81zNDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlNaWdyYXRpb24wHhcNMTUxMDI5MTMzMTM3WhcNMjUxMDI5MTMzMzE3WjAUMRIwEAYDVQQDDAlNaWdyYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb379tg4SD5EWUOAqNfSe/5QyRXxfToTVe9Ghu5CNco5XrXdUkhrUX+cZpte2GQze0LsNp0nnLW0HCEX5snMwDjDfzM18l2MSncNE0PrKlSfRIlmtLDqXe7fD6zm56aBNZRmTjrdevcdsPG2cb7i3ka4cDY6ro7Y4ueJVkpsNOmAXUfHMIFCIDGLaM0mbe0yVN03oP+dXdHCnZPXbnRPeV2QzdMMr82W6neg56PL6pEYSaH6ia4AE44nyJwJQtnlLYBuKp+iM/lk3dYSnIKpxC+x2inghrBRtMPLL0EITYrkIJnpmBBpKeklkZTL3Vys8lecji2WDkyHzyXxpKsEuxAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALaDK+wutEjdgY3Ux06Amp0k5qK16dz4jn+QKjdKPB1yThfzY1pisuyCUXPBlkn1OjB5ZvYl6ouwdNXgB8aeblbHZoyXh9ODeywi1xZd7pGxNXSfx0UzRk/YEEy0DAi9pxTyRYxiZ6/XJalS9PembTQvj+mVKqg1SDv7dyv4byvndEYSaUISrtGGrM3bb68PW4zInD793PJYWDSVxmEPOYtdgBJv4HAhPIJhjw15EOGlPv5QxW9P76OgISCutHaEe3UDP+TzIBBxYQFb1ZXA6ob3TFga78mFAkY4g98gEC11QSvZqhaRtLAz6PEisHRV+xDJVROgQ4Qew4qKgwE0gGE=", - "codeSecret" : "32f8634c-2be2-4d4d-8118-f4f7fee80b9f", - "roles" : { - "client" : { - "realm-management" : [ { - "id" : "dad7b3a4-b533-47c8-aba5-32e6429865a2", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false - }, { - "id" : "a1dd3971-3906-4f4a-b4cd-3a198d2d7150", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : false - }, { - "id" : "6c2d766f-cfa5-4cae-b1ca-81f1f9f242c8", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : false - }, { - "id" : "60bb5b3e-8067-43fe-803e-a7e367967c7c", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false - }, { - "id" : "c55cb35a-2602-47a6-a628-fc5a55341426", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false - }, { - "id" : "4dc834d0-766b-45aa-ab3b-b7b976baa65d", - "name" : "realm-admin", - "description" : "${role_realm-admin}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "view-users", "manage-identity-providers", "view-clients", "manage-realm", "manage-users", "impersonation", "view-realm", "view-events", "manage-clients", "manage-events", "view-identity-providers" ] - } - } - }, { - "id" : "d444a98f-ab5e-4857-9300-496e04e498f5", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false - }, { - "id" : "2f6f1407-f334-434f-becf-771e3ebb5625", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false - }, { - "id" : "a40d3211-5244-4d92-80c0-0d3215580250", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false - }, { - "id" : "d11c407e-504f-4923-b243-e794afa0247e", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false - }, { - "id" : "3ef6ace4-4e87-4c30-a8b3-1f0df25868c6", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false - }, { - "id" : "fa2a4972-b8d0-452e-8e13-d2cf7eaac7aa", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false - } ], - "security-admin-console" : [ ], - "broker" : [ { - "id" : "1bc5aeb4-1df1-4402-8195-e2a72f6dca30", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false - } ], - "account" : [ { - "id" : "71b5b5ff-b372-41a1-a427-7883fa64a8c7", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : false - }, { - "id" : "04daa556-8aeb-43ba-99c6-b393ec2a32d4", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false - } ] - } - }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "clientScopeMappings" : { - "realm-management" : [ { - "client" : "security-admin-console", - "roles" : [ "realm-admin" ] - } ] - }, - "clients" : [ { - "id" : "ba27336f-3f89-471d-98d2-b8856bd6dbf1", - "clientId" : "realm-management", - "name" : "${client_realm-management}", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "26aee4e9-8eec-421b-90a9-238538f5897a", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "5d56eba1-724e-4904-a8f8-86ca264a82cf", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "18a30786-89f9-4744-8f36-4de811a591ae", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "Claim JSON Type" : "String" - } - }, { - "id" : "4d1c4456-0c0d-49b9-bfba-c2c83645aeb2", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "Claim JSON Type" : "String" - } - }, { - "id" : "1a19db43-2346-4a24-b6f0-1b8d7fc1353e", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "a18612f0-9eb3-4d81-af0c-b0749b83fbd3", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "70c26044-c7fc-4090-98e1-670fef006e25", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "3fdddd5e-0022-4f6d-8fdf-212266db7fd4", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "baseUrl" : "/auth/admin/Migration/console/index.html", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "38ce8135-738d-4103-85ac-c3470ac8824d", - "redirectUris" : [ "/auth/admin/Migration/console/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "be58fe30-b767-4566-9192-a4fa81fafa2c", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "c97523fd-fd4f-48d7-8937-bd434fa374fd", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "2323a85d-2686-46d4-bea8-e36524920f2e", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "Claim JSON Type" : "String" - } - }, { - "id" : "56f8a80e-9e99-4add-b918-b864ca3f6f5c", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "Claim JSON Type" : "String" - } - }, { - "id" : "f9f51e8d-d5af-456c-be5a-3019fb8c0910", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "3d8fbb0c-9058-4dde-b675-ca77a153ceb8", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "068fcf1a-7048-43df-b3dd-e6c484e8b051", - "clientId" : "broker", - "name" : "${client_broker}", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "041b50a6-54b5-4cff-84ef-1b7c388d3395", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "57c6f779-c96b-4f03-b268-354af2a8731e", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "Claim JSON Type" : "String" - } - }, { - "id" : "f17a7b9f-9363-44bd-8320-df36f22ca712", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "ec1bf022-9e98-4f29-9bf0-f0a49bd844ad", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "0d54c616-326b-4fe7-bbfa-af9a28304dc5", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "Claim JSON Type" : "String" - } - }, { - "id" : "89ce95b9-a268-4306-a1ad-86066d0cdd03", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "8236274d-af69-4fc4-8804-a02d4af66157", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - } ] - }, { - "id" : "717b9e58-87ed-402d-a8f8-a37fd5e7c951", - "clientId" : "account", - "name" : "${client_account}", - "baseUrl" : "/auth/realms/Migration/account", - "surrogateAuthRequired" : false, - "enabled" : true, - "clientAuthenticatorType" : "client-secret", - "secret" : "9ea62eb5-5478-454a-a479-4012f8967f9c", - "defaultRoles" : [ "view-profile", "manage-account" ], - "redirectUris" : [ "/auth/realms/Migration/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "serviceAccountsEnabled" : false, - "directGrantsOnly" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "attributes" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "bfb4a165-2a55-4e4b-9b13-05e68822f5d6", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${familyName}", - "config" : { - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "Claim JSON Type" : "String" - } - }, { - "id" : "c505e313-d478-4b1d-94df-c2c9b6036a95", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - }, { - "id" : "12fc43f0-19b7-4b4a-b50a-40b6fc344ede", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${email}", - "config" : { - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "Claim JSON Type" : "String" - } - }, { - "id" : "250f3bf3-2655-4482-a814-3adcc7cef5a4", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : true, - "consentText" : "${fullName}", - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true" - } - }, { - "id" : "6cf70d19-6a9e-4abf-8917-38b87bac15d6", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${username}", - "config" : { - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "Claim JSON Type" : "String" - } - }, { - "id" : "6cdeae21-ca97-4723-b880-d5aa35fa77b0", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : true, - "consentText" : "${givenName}", - "config" : { - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "Claim JSON Type" : "String" - } - } ] - } ], - "browserSecurityHeaders" : { - "contentSecurityPolicy" : "frame-src 'self'", - "xFrameOptions" : "SAMEORIGIN" - }, - "smtpServer" : { }, - "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], - "enabledEventTypes" : [ ], - "adminEventsEnabled" : false, - "adminEventsDetailsEnabled" : false, - "identityFederationEnabled" : false, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "alias" : "registration form", - "description" : "registration form", - "providerId" : "form-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "registration-profile-action", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 40 - }, { - "authenticator" : "registration-password-action", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 50 - }, { - "authenticator" : "registration-recaptcha-action", - "autheticatorFlow" : false, - "requirement" : "DISABLED", - "userSetupAllowed" : false, - "priority" : 60 - } ] - }, { - "alias" : "direct grant", - "description" : "OpenID Connect Resource Owner Grant", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "direct-grant-validate-username", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "direct-grant-validate-password", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "direct-grant-validate-otp", - "autheticatorFlow" : false, - "requirement" : "OPTIONAL", - "userSetupAllowed" : false, - "priority" : 30 - } ] - }, { - "alias" : "reset credentials", - "description" : "Reset credentials for a user if they forgot their password or something", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "reset-credentials-choose-user", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "reset-credential-email", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "authenticator" : "reset-password", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 30 - }, { - "authenticator" : "reset-otp", - "autheticatorFlow" : false, - "requirement" : "OPTIONAL", - "userSetupAllowed" : false, - "priority" : 40 - } ] - }, { - "alias" : "forms", - "description" : "Username, password, otp and other auth forms.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-username-password-form", - "autheticatorFlow" : false, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "auth-otp-form", - "autheticatorFlow" : false, - "requirement" : "OPTIONAL", - "userSetupAllowed" : false, - "priority" : 20 - } ] - }, { - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "client-jwt", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 20 - } ] - }, { - "alias" : "browser", - "description" : "browser based authentication", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-cookie", - "autheticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 10 - }, { - "authenticator" : "auth-spnego", - "autheticatorFlow" : false, - "requirement" : "DISABLED", - "userSetupAllowed" : false, - "priority" : 20 - }, { - "flowAlias" : "forms", - "autheticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "userSetupAllowed" : false, - "priority" : 30 - } ] - }, { - "alias" : "registration", - "description" : "registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "flowAlias" : "registration form", - "autheticatorFlow" : true, - "requirement" : "REQUIRED", - "userSetupAllowed" : false, - "priority" : 10 - } ] - } ], - "authenticatorConfig" : [ ], - "requiredActions" : [ { - "alias" : "CONFIGURE_TOTP", - "name" : "Configure Totp", - "providerId" : "CONFIGURE_TOTP", - "enabled" : true, - "defaultAction" : false, - "config" : { } - }, { - "alias" : "VERIFY_EMAIL", - "name" : "Verify Email", - "providerId" : "VERIFY_EMAIL", - "enabled" : true, - "defaultAction" : false, - "config" : { } - }, { - "alias" : "terms_and_conditions", - "name" : "Terms and Conditions", - "providerId" : "terms_and_conditions", - "enabled" : false, - "defaultAction" : false, - "config" : { } - }, { - "alias" : "UPDATE_PASSWORD", - "name" : "Update Password", - "providerId" : "UPDATE_PASSWORD", - "enabled" : true, - "defaultAction" : false, - "config" : { } - }, { - "alias" : "UPDATE_PROFILE", - "name" : "Update Profile", - "providerId" : "UPDATE_PROFILE", - "enabled" : true, - "defaultAction" : false, - "config" : { } - } ], - "browserFlow" : "browser", - "registrationFlow" : "registration", - "directGrantFlow" : "direct grant", - "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients" -} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 40e17b5572..64271ef79f 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -54,7 +54,6 @@ 10090 10099 false - 60 -Xms64m -Xmx512m -XX:MetaspaceSize=96M -XX:MaxMetaspaceSize=256m serverConfig org.jboss.as.arquillian.container.managed.ManagedDeployableContainer @@ -76,6 +75,7 @@ ${project.build.directory}/dependency/test-constants.properties + false @@ -116,6 +116,7 @@ copy-resources + ${skip.add.user.json} ${auth.server.config.dir} @@ -402,7 +403,7 @@ - + migration @@ -411,30 +412,8 @@ migrated.auth.server.version - - - - - - - maven-enforcer-plugin - - - enforce-properties - - enforce - - - - - migrated.auth.server.version - - - - - - maven-dependency-plugin @@ -448,7 +427,7 @@ org.keycloak.testsuite - ${migrated.auth.server.jboss.artifactId} + integration-arquillian-migration-server ${project.version} zip @@ -464,158 +443,16 @@ ${migrated.auth.server.version} + true + ${containers.home}/keycloak-${migrated.auth.server.version} + src/test/resources/migration-test/migration-realm-${migrated.auth.server.version}.json - - - migration-kc16 - - - migrated.auth.server.version - 1.6.1.Final - - - - integration-arquillian-server-wildfly-kc16 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - src/test/resources/migration-test/migration-realm-16.json - - - - - - - - - - migration-kc15 - - - migrated.auth.server.version - 1.5.1.Final - - - - integration-arquillian-server-wildfly-kc15 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - src/test/resources/migration-test/migration-realm-15.json - - - - - - - - - - migration-kc14 - - - migrated.auth.server.version - 1.4.0.Final - - - - integration-arquillian-server-wildfly-kc14 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - - - - - - - - - - migration-kc13 - - - migrated.auth.server.version - 1.3.1.Final - - - - integration-arquillian-server-wildfly-kc13 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - - - - - - - - - - migration-kc12 - - - migrated.auth.server.version - 1.2.0.Final - - - - integration-arquillian-server-wildfly-kc12 - - - - - - maven-surefire-plugin - - - true - ${containers.home}/keycloak-${migrated.auth.server.version} - - - - - - - - - - - + no-account From c867fcf9e79321e3a781c329db04e4666693b07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Paris?= Date: Mon, 5 Sep 2016 17:12:21 +0200 Subject: [PATCH 44/49] Remove duplicate untranslated word --- .../theme/base/admin/messages/admin-messages_fr.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties index 93e7b14d5b..b923234bca 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_fr.properties @@ -27,7 +27,7 @@ sslRequired.option.all=toutes les requ\u00eates sslRequired.option.external=les requ\u00eates externes sslRequired.option.none=aucun sslRequired.tooltip=Si le HTTPS est requis ? ''aucun'' signifie que le HTTPS n''est requis pour aucune adresse IP cliente. ''les requ\u00eates externes'' signifie que localhost et les adresses IP priv\u00e9es peuvent acc\u00e9der sans HTTPS. ''toutes les requ\u00eates'' signifie que le protocole HTTPS est obligatoire pour toutes les adresses IP. -publicKey=Clef publique key +publicKey=Clef publique gen-new-keys=Cr\u00e9ation de nouvelle clef certificate=Certificat host=H\u00f4te From 03c05bd72b607eca8809d41abdb3b1bcf6782b17 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 5 Sep 2016 18:04:24 +0200 Subject: [PATCH 45/49] KEYCLOAK-2957 IdpEmailVerificationAuthenticator should setEmailVerified to true after successfuly link user by email verification --- .../broker/IdpEmailVerificationAuthenticator.java | 3 +++ .../testsuite/broker/AbstractFirstBrokerLoginTest.java | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java index 620e0c9f87..e56e7e6e31 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java @@ -132,6 +132,9 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator clientSession.setNote(IS_DIFFERENT_BROWSER, "true"); } + // User successfully confirmed linking by email verification. His email was defacto verified + existingUser.setEmailVerified(true); + context.setUser(existingUser); context.success(); } else { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java index 3b1b440920..67b82bb046 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java @@ -287,6 +287,10 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi // authenticated and redirected to app. User is linked with identity provider assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor"); + + // Assert user's email is verified now + UserModel user = getFederatedUser(); + Assert.assertTrue(user.isEmailVerified()); } From e18f3edbcdb9a623489c5eb79d3134a55ea88635 Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 6 Sep 2016 07:13:41 +0200 Subject: [PATCH 46/49] KEYCLOAK-3526 Fuse adapter ZIP fix --- distribution/adapters/fuse-adapter-zip/pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/distribution/adapters/fuse-adapter-zip/pom.xml b/distribution/adapters/fuse-adapter-zip/pom.xml index 86128fc00c..c64972085d 100644 --- a/distribution/adapters/fuse-adapter-zip/pom.xml +++ b/distribution/adapters/fuse-adapter-zip/pom.xml @@ -21,7 +21,7 @@ keycloak-parent org.keycloak - 2.1.0-SNAPSHOT + 2.2.0-SNAPSHOT ../../../pom.xml @@ -35,6 +35,8 @@ org.keycloak keycloak-osgi-features ${project.version} + xml + features org.keycloak @@ -85,6 +87,10 @@ org.keycloak keycloak-core + + org.keycloak + keycloak-authz-client + org.keycloak keycloak-adapter-spi From 8c5b1e489245b026aa51303e24e875b1d44c1d7a Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 5 Sep 2016 23:12:14 +0200 Subject: [PATCH 47/49] KEYCLOAK-3525 Validation callback when creating/updating protocolMapper --- .../org/keycloak/protocol/ProtocolMapper.java | 16 +++++++ .../ProtocolMapperConfigException.java | 48 +++++++++++++++++++ .../oidc/mappers/UserAttributeMapper.java | 2 + .../resources/admin/ClientResource.java | 2 +- .../admin/ClientTemplateResource.java | 2 +- .../admin/ProtocolMappersResource.java | 34 ++++++++++++- 6 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 server-spi/src/main/java/org/keycloak/protocol/ProtocolMapperConfigException.java diff --git a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java b/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java index 1942422269..8055fae82d 100755 --- a/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java +++ b/server-spi/src/main/java/org/keycloak/protocol/ProtocolMapper.java @@ -17,6 +17,10 @@ package org.keycloak.protocol; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperContainerModel; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ConfiguredProvider; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; @@ -30,4 +34,16 @@ public interface ProtocolMapper extends Provider, ProviderFactoryMarek Posolda + */ +public class ProtocolMapperConfigException extends Exception { + + private Object[] parameters; + + public ProtocolMapperConfigException(String message) { + super(message); + } + + public ProtocolMapperConfigException(String message, Throwable cause) { + super(message, cause); + } + + public ProtocolMapperConfigException(String message, Object ... parameters) { + super(message); + this.parameters = parameters; + } + + public Object[] getParameters() { + return parameters; + } + + public void setParameters(Object[] parameters) { + this.parameters = parameters; + } + +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java index 56e7a48d2e..f7475a95cf 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/UserAttributeMapper.java @@ -19,10 +19,12 @@ package org.keycloak.protocol.oidc.mappers; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperContainerModel; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.ProtocolMapperConfigException; import org.keycloak.protocol.ProtocolMapperUtils; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.AccessToken; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index dd17b07a8e..9b6593f548 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -107,7 +107,7 @@ public class ClientResource { @Path("protocol-mappers") public ProtocolMappersResource getProtocolMappers() { - ProtocolMappersResource mappers = new ProtocolMappersResource(client, auth, adminEvent); + ProtocolMappersResource mappers = new ProtocolMappersResource(realm, client, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(mappers); return mappers; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java index baf9bb65f3..761e307f9c 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientTemplateResource.java @@ -81,7 +81,7 @@ public class ClientTemplateResource { @Path("protocol-mappers") public ProtocolMappersResource getProtocolMappers() { - ProtocolMappersResource mappers = new ProtocolMappersResource(template, auth, adminEvent); + ProtocolMappersResource mappers = new ProtocolMappersResource(realm, template, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(mappers); return mappers; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java index b9da2bf107..86dcefcfe5 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ProtocolMappersResource.java @@ -20,14 +20,22 @@ import org.jboss.resteasy.annotations.cache.NoCache; import org.jboss.resteasy.spi.NotFoundException; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; +import org.keycloak.mappers.FederationConfigValidationException; +import org.keycloak.mappers.UserFederationMapper; +import org.keycloak.mappers.UserFederationMapperFactory; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ProtocolMapperContainerModel; import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.protocol.ProtocolMapper; +import org.keycloak.protocol.ProtocolMapperConfigException; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.services.ErrorResponse; +import org.keycloak.services.ErrorResponseException; import org.keycloak.services.ServicesLogger; import org.keycloak.services.resources.admin.RealmAuth.Resource; @@ -44,8 +52,10 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; +import java.text.MessageFormat; import java.util.LinkedList; import java.util.List; +import java.util.Properties; /** * Base resource for managing users @@ -56,6 +66,8 @@ import java.util.List; public class ProtocolMappersResource { protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + protected RealmModel realm; + protected ProtocolMapperContainerModel client; protected RealmAuth auth; @@ -68,7 +80,8 @@ public class ProtocolMappersResource { @Context protected KeycloakSession session; - public ProtocolMappersResource(ProtocolMapperContainerModel client, RealmAuth auth, AdminEventBuilder adminEvent) { + public ProtocolMappersResource(RealmModel realm, ProtocolMapperContainerModel client, RealmAuth auth, AdminEventBuilder adminEvent) { + this.realm = realm; this.auth = auth; this.client = client; this.adminEvent = adminEvent.resource(ResourceType.PROTOCOL_MAPPER); @@ -119,6 +132,7 @@ public class ProtocolMappersResource { ProtocolMapperModel model = null; try { model = RepresentationToModel.toModel(rep); + validateModel(model); model = client.addProtocolMapper(model); adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success(); @@ -146,6 +160,7 @@ public class ProtocolMappersResource { ProtocolMapperModel model = null; for (ProtocolMapperRepresentation rep : reps) { model = RepresentationToModel.toModel(rep); + validateModel(model); model = client.addProtocolMapper(model); } adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(reps).success(); @@ -216,6 +231,9 @@ public class ProtocolMappersResource { ProtocolMapperModel model = client.getProtocolMapperById(id); if (model == null) throw new NotFoundException("Model not found"); model = RepresentationToModel.toModel(rep); + + validateModel(model); + client.updateProtocolMapper(model); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); } @@ -242,4 +260,18 @@ public class ProtocolMappersResource { } + private void validateModel(ProtocolMapperModel model) { + try { + ProtocolMapper mapper = (ProtocolMapper)session.getKeycloakSessionFactory().getProviderFactory(ProtocolMapper.class, model.getProtocolMapper()); + if (mapper != null) { + mapper.validateConfig(session, realm, client, model); + } + } catch (ProtocolMapperConfigException ex) { + logger.error(ex.getMessage()); + Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale()); + throw new ErrorResponseException(ex.getMessage(), MessageFormat.format(messages.getProperty(ex.getMessage(), ex.getMessage()), ex.getParameters()), + Response.Status.BAD_REQUEST); + } + } + } From 7a66b055bea2daabab31cdb1936e290eb588ca6e Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Tue, 6 Sep 2016 08:59:58 +0200 Subject: [PATCH 48/49] KEYCLOAK-3475 Call event handlers when initialized with tokens --- adapters/oidc/js/src/main/resources/keycloak.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js index 577452e634..c6adb9b67a 100755 --- a/adapters/oidc/js/src/main/resources/keycloak.js +++ b/adapters/oidc/js/src/main/resources/keycloak.js @@ -160,15 +160,25 @@ if (loginIframe.enable) { setupCheckLoginIframe().success(function() { checkLoginIframe().success(function () { + kc.onAuthSuccess && kc.onAuthSuccess(); initPromise.setSuccess(); }).error(function () { + kc.onAuthError && kc.onAuthError(); if (initOptions.onLoad) { onLoad(); } }); }); } else { - initPromise.setSuccess(); + kc.updateToken(-1).success(function() { + kc.onAuthSuccess && kc.onAuthSuccess(); + initPromise.setSuccess(); + }).error(function() { + kc.onAuthError && kc.onAuthError(); + if (initOptions.onLoad) { + onLoad(); + } + }); } } else if (initOptions.onLoad) { onLoad(); @@ -368,7 +378,7 @@ minValidity = minValidity || 5; var exec = function() { - if (!kc.isTokenExpired(minValidity)) { + if (minValidity >= 0 && !kc.isTokenExpired(minValidity)) { promise.setSuccess(false); } else { var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; @@ -1056,7 +1066,7 @@ if (!(this instanceof CookieStorage)) { return new CookieStorage(); } - + var cs = this; cs.get = function(state) { From fbb2dfcf59a41d227f65613a538c8b7421c24b2d Mon Sep 17 00:00:00 2001 From: mposolda Date: Tue, 6 Sep 2016 10:46:27 +0200 Subject: [PATCH 49/49] Fuse adapter ZIP download --- distribution/downloads/pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/distribution/downloads/pom.xml b/distribution/downloads/pom.xml index e1356d689a..71b27d9b63 100755 --- a/distribution/downloads/pom.xml +++ b/distribution/downloads/pom.xml @@ -262,6 +262,17 @@ keycloak-wildfly-adapter-dist tar.gz + + + org.keycloak + keycloak-fuse-adapter-dist + zip + + + org.keycloak + keycloak-fuse-adapter-dist + tar.gz + target/${project.version}/adapters/keycloak-oidc