From 444cdac9f5b2c9e07ba35d805bc023f12f65044f Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 6 Mar 2015 20:32:50 +0100 Subject: [PATCH 1/7] KEYCLOAK-1043 Fix fuse packaging due to bouncycastle update. Improve camel example --- .../features/src/main/resources/features.xml | 3 +- examples/fuse/README.md | 3 +- examples/fuse/camel/pom.xml | 5 +++ .../org/keycloak/example/CamelHelloBean.java | 15 ------- .../keycloak/example/CamelHelloProcessor.java | 40 +++++++++++++++++++ .../OSGI-INF/blueprint/blueprint.xml | 7 +--- .../org/keycloak/example/CamelClient.java | 2 +- .../src/main/webapp/customers/camel.jsp | 2 +- 8 files changed, 53 insertions(+), 24 deletions(-) delete mode 100644 examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloBean.java create mode 100644 examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloProcessor.java diff --git a/distribution/osgi/features/src/main/resources/features.xml b/distribution/osgi/features/src/main/resources/features.xml index d56602b95b..7cda6d134a 100644 --- a/distribution/osgi/features/src/main/resources/features.xml +++ b/distribution/osgi/features/src/main/resources/features.xml @@ -4,7 +4,8 @@
The keycloak adapter core stuff
mvn:org.keycloak/keycloak-osgi-thirdparty/${project.version} - mvn:org.bouncycastle/bcprov-jdk16/${bouncycastle.version} + mvn:org.bouncycastle/bcprov-jdk15on/${bouncycastle.crypto.version} + mvn:org.bouncycastle/bcpkix-jdk15on/${bouncycastle.crypto.version} mvn:org.codehaus.jackson/jackson-core-asl/${jackson.version} mvn:org.codehaus.jackson/jackson-mapper-asl/${jackson.version} mvn:org.codehaus.jackson/jackson-xc/${jackson.version} diff --git a/examples/fuse/README.md b/examples/fuse/README.md index 8b1508cc65..d9c81c519d 100644 --- a/examples/fuse/README.md +++ b/examples/fuse/README.md @@ -31,7 +31,8 @@ Base steps ---------- * Run external instance of Keycloak server on WildFly 8 or JBoss EAP 6.3 . Fuse demo suppose that server is running on [http://localhost:8080/auth](http://localhost:8080/auth) -* Import realm `demo` from the file testrealm.json on `examples/fuse/testrealm.json` . +* Import realm `demo` from the file testrealm.json on `examples/fuse/testrealm.json` . See [here](../demo-template/README.md#step-3-import-the-test-realm) +the details on how to import the realm * Then build examples, which is needed so the feature repository is added to your local maven repo: ``` diff --git a/examples/fuse/camel/pom.xml b/examples/fuse/camel/pom.xml index a81554e88c..568e281132 100644 --- a/examples/fuse/camel/pom.xml +++ b/examples/fuse/camel/pom.xml @@ -30,6 +30,11 @@ + + org.keycloak + keycloak-core + ${project.version} + org.apache.camel camel-core diff --git a/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloBean.java b/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloBean.java deleted file mode 100644 index df5c472e68..0000000000 --- a/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloBean.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.keycloak.example; - -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * @author Marek Posolda - */ -public class CamelHelloBean { - - public String hello() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - return "Hello admin! It's " + sdf.format(new Date()); - } -} diff --git a/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloProcessor.java b/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloProcessor.java new file mode 100644 index 0000000000..1009c5cf69 --- /dev/null +++ b/examples/fuse/camel/src/main/java/org/keycloak/example/CamelHelloProcessor.java @@ -0,0 +1,40 @@ +package org.keycloak.example; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.security.sasl.Sasl; +import javax.servlet.http.HttpServletRequest; + +import org.apache.camel.Exchange; +import org.apache.camel.Processor; +import org.ietf.jgss.GSSCredential; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.constants.KerberosConstants; +import org.keycloak.representations.AccessToken; +import org.keycloak.util.KerberosSerializationUtils; + +/** + * @author Marek Posolda + */ +public class CamelHelloProcessor implements Processor { + + private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + @Override + public void process(Exchange exchange) throws Exception { + HttpServletRequest req = exchange.getIn().getBody(HttpServletRequest.class); + KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) req.getUserPrincipal(); + AccessToken accessToken = keycloakPrincipal.getKeycloakSecurityContext().getToken(); + String username = accessToken.getPreferredUsername(); + + // send a html response with fullName from LDAP + exchange.getOut().setBody("Hello " + username + "! It's " + sdf.format(new Date()) + ""); + } +} diff --git a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 0dc0fdd1e4..f2cecf90a4 100644 --- a/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml +++ b/examples/fuse/camel/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -49,18 +49,15 @@ - + - - - + - diff --git a/examples/fuse/customer-app-fuse/src/main/java/org/keycloak/example/CamelClient.java b/examples/fuse/customer-app-fuse/src/main/java/org/keycloak/example/CamelClient.java index 9ddb8f19d5..6e27e30aa5 100644 --- a/examples/fuse/customer-app-fuse/src/main/java/org/keycloak/example/CamelClient.java +++ b/examples/fuse/customer-app-fuse/src/main/java/org/keycloak/example/CamelClient.java @@ -30,7 +30,7 @@ public class CamelClient { try { HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() != 200) { - return "There was a failure processing request. You either didn't configure Keycloak properly or you don't have enought permission? Status code is " + return "There was a failure processing request. You either didn't configure Keycloak properly or you don't have admin permission? Status code is " + response.getStatusLine().getStatusCode(); } HttpEntity entity = response.getEntity(); diff --git a/examples/fuse/customer-app-fuse/src/main/webapp/customers/camel.jsp b/examples/fuse/customer-app-fuse/src/main/webapp/customers/camel.jsp index 1d29927f27..6b7c5913b6 100755 --- a/examples/fuse/customer-app-fuse/src/main/webapp/customers/camel.jsp +++ b/examples/fuse/customer-app-fuse/src/main/webapp/customers/camel.jsp @@ -8,7 +8,7 @@ Camel page -

You will receive info from camel endpoint. Endpoint is accessible just for admin user

+

You will receive info from camel endpoint. Endpoint is accessible just for user with admin role

Response from camel: <%= CamelClient.sendRequest(request) %>



From a64ce244d559b5cde5300d29fc8624d1420bb855 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraci=20Paix=C3=A3o=20Kr=C3=B6hling?= Date: Fri, 13 Mar 2015 09:29:14 +0100 Subject: [PATCH 2/7] KEYCLOAK-1101 - Added notUsername() to password policy. --- .../theme/admin/base/resources/js/services.js | 12 ++-- .../resources/partials/realm-credentials.html | 4 +- .../org/keycloak/models/PasswordPolicy.java | 32 +++++++--- .../models/UserFederationManager.java | 2 +- .../keycloak/models/PasswordPolicyTest.java | 62 +++++++++++-------- .../services/validation/Validation.java | 2 +- 6 files changed, 70 insertions(+), 44 deletions(-) diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js index b3b0edfcee..53bcf1f1fa 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/services.js @@ -1042,7 +1042,8 @@ module.factory('PasswordPolicy', function() { digits: "Minimal number (integer type) of digits in password. Default value is 1.", lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.", upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.", - specialChars: "Minimal number (integer type) of special characters in password. Default value is 1." + specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.", + notUsername: "Block passwords that are equal to the username" } p.allPolicies = [ @@ -1051,7 +1052,8 @@ module.factory('PasswordPolicy', function() { { name: 'digits', value: 1 }, { name: 'lowerCase', value: 1 }, { name: 'upperCase', value: 1 }, - { name: 'specialChars', value: 1 } + { name: 'specialChars', value: 1 }, + { name: 'notUsername', value: 1 } ]; p.parse = function(policyString) { @@ -1068,9 +1070,9 @@ module.factory('PasswordPolicy', function() { var re = /(\w+)\(*(\d*)\)*/; var policyEntry = re.exec(policyToken); - - policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) }); - + if (null !== policyEntry) { + policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) }); + } } return policies; diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-credentials.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-credentials.html index 6dff85cba7..608a2b622f 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-credentials.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-credentials.html @@ -46,7 +46,9 @@ - +
diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java index dc82bcc5f5..3651e9d3f4 100755 --- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java +++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java @@ -52,6 +52,8 @@ public class PasswordPolicy { list.add(new UpperCase(args)); } else if (name.equals(SpecialChars.NAME)) { list.add(new SpecialChars(args)); + } else if (name.equals(NotUsername.NAME)) { + list.add(new NotUsername(args)); } else if (name.equals(HashIterations.NAME)) { list.add(new HashIterations(args)); } @@ -74,9 +76,9 @@ public class PasswordPolicy { return -1; } - public String validate(String password) { + public String validate(String username, String password) { for (Policy p : policies) { - String error = p.validate(password); + String error = p.validate(username, password); if (error != null) { return error; } @@ -85,7 +87,7 @@ public class PasswordPolicy { } private static interface Policy { - public String validate(String password); + public String validate(String username, String password); } private static class HashIterations implements Policy { @@ -97,11 +99,23 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { return null; } } + private static class NotUsername implements Policy { + private static final String NAME = "notUsername"; + + public NotUsername(String[] args) { + } + + @Override + public String validate(String username, String password) { + return username.equals(password) ? "Invalid password: must not be equal to the username" : null; + } + } + private static class Length implements Policy { private static final String NAME = "length"; private int min; @@ -111,7 +125,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { return password.length() < min ? "Invalid password: minimum length " + min : null; } } @@ -125,7 +139,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isDigit(c)) { @@ -145,7 +159,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isLowerCase(c)) { @@ -165,7 +179,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isUpperCase(c)) { @@ -185,7 +199,7 @@ public class PasswordPolicy { } @Override - public String validate(String password) { + public String validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (!Character.isLetterOrDigit(c)) { diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java index fe96e5af16..a360f958a4 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java @@ -323,7 +323,7 @@ public class UserFederationManager implements UserProvider { public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) { if (credential.getType().equals(UserCredentialModel.PASSWORD)) { if (realm.getPasswordPolicy() != null) { - String error = realm.getPasswordPolicy().validate(credential.getValue()); + String error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue()); if (error != null) throw new ModelException(error); } } diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java index 3481498884..7242c554df 100644 --- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java +++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java @@ -11,68 +11,76 @@ public class PasswordPolicyTest { @Test public void testLength() { PasswordPolicy policy = new PasswordPolicy("length"); - Assert.assertEquals("Invalid password: minimum length 8", policy.validate("1234567")); - Assert.assertNull(policy.validate("12345678")); + Assert.assertEquals("Invalid password: minimum length 8", policy.validate("jdoe", "1234567")); + Assert.assertNull(policy.validate("jdoe", "12345678")); policy = new PasswordPolicy("length(4)"); - Assert.assertEquals("Invalid password: minimum length 4", policy.validate("123")); - Assert.assertNull(policy.validate("1234")); + Assert.assertEquals("Invalid password: minimum length 4", policy.validate("jdoe", "123")); + Assert.assertNull(policy.validate("jdoe", "1234")); } @Test public void testDigits() { PasswordPolicy policy = new PasswordPolicy("digits"); - Assert.assertEquals("Invalid password: must contain at least 1 numerical digits", policy.validate("abcd")); - Assert.assertNull(policy.validate("abcd1")); + Assert.assertEquals("Invalid password: must contain at least 1 numerical digits", policy.validate("jdoe", "abcd")); + Assert.assertNull(policy.validate("jdoe", "abcd1")); policy = new PasswordPolicy("digits(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 numerical digits", policy.validate("abcd1")); - Assert.assertNull(policy.validate("abcd12")); + Assert.assertEquals("Invalid password: must contain at least 2 numerical digits", policy.validate("jdoe", "abcd1")); + Assert.assertNull(policy.validate("jdoe", "abcd12")); } @Test public void testLowerCase() { PasswordPolicy policy = new PasswordPolicy("lowerCase"); - Assert.assertEquals("Invalid password: must contain at least 1 lower case characters", policy.validate("ABCD1234")); - Assert.assertNull(policy.validate("ABcD1234")); + Assert.assertEquals("Invalid password: must contain at least 1 lower case characters", policy.validate("jdoe", "ABCD1234")); + Assert.assertNull(policy.validate("jdoe", "ABcD1234")); policy = new PasswordPolicy("lowerCase(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 lower case characters", policy.validate("ABcD1234")); - Assert.assertNull(policy.validate("aBcD1234")); + Assert.assertEquals("Invalid password: must contain at least 2 lower case characters", policy.validate("jdoe", "ABcD1234")); + Assert.assertNull(policy.validate("jdoe", "aBcD1234")); } @Test public void testUpperCase() { PasswordPolicy policy = new PasswordPolicy("upperCase"); - Assert.assertEquals("Invalid password: must contain at least 1 upper case characters", policy.validate("abcd1234")); - Assert.assertNull(policy.validate("abCd1234")); + Assert.assertEquals("Invalid password: must contain at least 1 upper case characters", policy.validate("jdoe", "abcd1234")); + Assert.assertNull(policy.validate("jdoe", "abCd1234")); policy = new PasswordPolicy("upperCase(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 upper case characters", policy.validate("abCd1234")); - Assert.assertNull(policy.validate("AbCd1234")); + Assert.assertEquals("Invalid password: must contain at least 2 upper case characters", policy.validate("jdoe", "abCd1234")); + Assert.assertNull(policy.validate("jdoe", "AbCd1234")); } @Test public void testSpecialChars() { PasswordPolicy policy = new PasswordPolicy("specialChars"); - Assert.assertEquals("Invalid password: must contain at least 1 special characters", policy.validate("abcd1234")); - Assert.assertNull(policy.validate("ab&d1234")); + Assert.assertEquals("Invalid password: must contain at least 1 special characters", policy.validate("jdoe", "abcd1234")); + Assert.assertNull(policy.validate("jdoe", "ab&d1234")); policy = new PasswordPolicy("specialChars(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 special characters", policy.validate("ab&d1234")); - Assert.assertNull(policy.validate("ab&d-234")); + Assert.assertEquals("Invalid password: must contain at least 2 special characters", policy.validate("jdoe", "ab&d1234")); + Assert.assertNull(policy.validate("jdoe", "ab&d-234")); + } + + @Test + public void testNotUsername() { + PasswordPolicy policy = new PasswordPolicy("notUsername"); + Assert.assertEquals("Invalid password: must not be equal to the username", policy.validate("jdoe", "jdoe")); + Assert.assertNull(policy.validate("jdoe", "ab&d1234")); } @Test public void testComplex() { - PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2)"); - Assert.assertNotNull(policy.validate("12aaBB&")); - Assert.assertNotNull(policy.validate("aaaaBB&-")); - Assert.assertNotNull(policy.validate("12AABB&-")); - Assert.assertNotNull(policy.validate("12aabb&-")); - Assert.assertNotNull(policy.validate("12aaBBcc")); + PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()"); + Assert.assertNotNull(policy.validate("jdoe", "12aaBB&")); + Assert.assertNotNull(policy.validate("jdoe", "aaaaBB&-")); + Assert.assertNotNull(policy.validate("jdoe", "12AABB&-")); + Assert.assertNotNull(policy.validate("jdoe", "12aabb&-")); + Assert.assertNotNull(policy.validate("jdoe", "12aaBBcc")); + Assert.assertNotNull(policy.validate("12aaBB&-", "12aaBB&-")); - Assert.assertNull(policy.validate("12aaBB&-")); + Assert.assertNull(policy.validate("jdoe", "12aaBB&-")); } } diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java index 254cee1e8c..d306dc050d 100755 --- a/services/src/main/java/org/keycloak/services/validation/Validation.java +++ b/services/src/main/java/org/keycloak/services/validation/Validation.java @@ -49,7 +49,7 @@ public class Validation { } public static String validatePassword(MultivaluedMap formData, PasswordPolicy policy) { - return policy.validate(formData.getFirst("password")); + return policy.validate(formData.getFirst("username"), formData.getFirst("password")); } public static String validateUpdateProfileForm(MultivaluedMap formData) { From 84eed9ffc6184b8fe4e49689029a17eb7ba3529c Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 13 Mar 2015 17:16:46 +0100 Subject: [PATCH 3/7] Fix kerberos test failing in some environments --- .../ldap/EmbeddedServersFactory.java | 19 +++++++++++++++++-- .../ldap/KerberosEmbeddedServer.java | 8 +++++--- .../testutils/ldap/LDAPEmbeddedServer.java | 13 +++++++------ .../resources/kerberos/users-kerberos.ldif | 2 +- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java index d739b6c3a8..4c3fab61d2 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/EmbeddedServersFactory.java @@ -1,5 +1,8 @@ package org.keycloak.testutils.ldap; +import java.net.InetAddress; +import java.net.UnknownHostException; + /** * Factory for ApacheDS based LDAP and Kerberos servers * @@ -21,6 +24,7 @@ public class EmbeddedServersFactory { private String baseDN; private String bindHost; private int bindPort; + private String ldapSaslPrincipal; private String ldifFile; private String kerberosRealm; private int kdcPort; @@ -39,6 +43,7 @@ public class EmbeddedServersFactory { this.bindHost = System.getProperty("ldap.host"); String bindPort = System.getProperty("ldap.port"); this.ldifFile = System.getProperty("ldap.ldif"); + this.ldapSaslPrincipal = System.getProperty("ldap.saslPrincipal"); this.kerberosRealm = System.getProperty("kerberos.realm"); String kdcPort = System.getProperty("kerberos.port"); @@ -62,6 +67,16 @@ public class EmbeddedServersFactory { if (kdcEncryptionTypes == null || kdcEncryptionTypes.isEmpty()) { kdcEncryptionTypes = DEFAULT_KDC_ENCRYPTION_TYPES; } + + if (ldapSaslPrincipal == null || ldapSaslPrincipal.isEmpty()) { + try { + // Same algorithm like sun.security.krb5.PrincipalName constructor + String canonicalHost = (InetAddress.getByName(bindHost)).getCanonicalHostName(); + this.ldapSaslPrincipal = "ldap/" + canonicalHost + "@" + kerberosRealm; + } catch (UnknownHostException uhe) { + throw new RuntimeException(uhe); + } + } } @@ -72,7 +87,7 @@ public class EmbeddedServersFactory { ldifFile = DEFAULT_LDIF_FILE; } - return new LDAPEmbeddedServer(baseDN, bindHost, bindPort, ldifFile); + return new LDAPEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, ldapSaslPrincipal); } @@ -83,6 +98,6 @@ public class EmbeddedServersFactory { ldifFile = DEFAULT_KERBEROS_LDIF_FILE; } - return new KerberosEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, kerberosRealm, kdcPort, kdcEncryptionTypes); + return new KerberosEmbeddedServer(baseDN, bindHost, bindPort, ldifFile, ldapSaslPrincipal, kerberosRealm, kdcPort, kdcEncryptionTypes); } } diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java index f5683425c0..63cc027f7f 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/KerberosEmbeddedServer.java @@ -2,6 +2,7 @@ package org.keycloak.testutils.ldap; import java.io.IOException; import java.lang.reflect.Field; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; @@ -48,8 +49,8 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer { } - protected KerberosEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String kerberosRealm, int kdcPort, String kdcEncryptionTypes) { - super(baseDN, bindHost, bindPort, ldifFile); + protected KerberosEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String ldapSaslPrincipal, String kerberosRealm, int kdcPort, String kdcEncryptionTypes) { + super(baseDN, bindHost, bindPort, ldifFile, ldapSaslPrincipal); this.kdcEncryptionTypes = kdcEncryptionTypes; this.kerberosRealm = kerberosRealm; this.kdcPort = kdcPort; @@ -79,7 +80,8 @@ public class KerberosEmbeddedServer extends LDAPEmbeddedServer { LdapServer ldapServer = super.createLdapServer(); ldapServer.setSaslHost( this.bindHost ); - ldapServer.setSaslPrincipal( "ldap/" + this.bindHost + "@" + this.kerberosRealm); + ldapServer.setSaslPrincipal( this.ldapSaslPrincipal); + ldapServer.setSaslRealms(new ArrayList()); ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.PLAIN, new PlainMechanismHandler()); ldapServer.addSaslMechanismHandler(SupportedSaslMechanisms.CRAM_MD5, new CramMd5MechanismHandler()); diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java index 4fde15bd3e..e6e7be1b1d 100644 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/ldap/LDAPEmbeddedServer.java @@ -2,7 +2,6 @@ package org.keycloak.testutils.ldap; import java.io.File; import java.io.InputStream; -import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -35,6 +34,7 @@ public class LDAPEmbeddedServer { protected final String bindHost; protected final int bindPort; protected final String ldifFile; + protected final String ldapSaslPrincipal; protected DirectoryService directoryService; protected LdapServer ldapServer; @@ -47,16 +47,19 @@ public class LDAPEmbeddedServer { ldapEmbeddedServer.start(); } - public LDAPEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile) { + public LDAPEmbeddedServer(String baseDN, String bindHost, int bindPort, String ldifFile, String ldapSaslPrincipal) { this.baseDN = baseDN; this.bindHost = bindHost; this.bindPort = bindPort; this.ldifFile = ldifFile; + this.ldapSaslPrincipal = ldapSaslPrincipal; } public void init() throws Exception { - log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort); + log.info("Creating LDAP Directory Service. Config: baseDN=" + baseDN + ", bindHost=" + bindHost + ", bindPort=" + bindPort + + ", ldapSaslPrincipal=" + ldapSaslPrincipal); + this.directoryService = createDirectoryService(); log.info("Importing LDIF: " + ldifFile); @@ -133,9 +136,6 @@ public class LDAPEmbeddedServer { // Propagate the anonymous flag to the DS directoryService.setAllowAnonymousAccess(false); - ldapServer.setSaslHost( this.bindHost ); - ldapServer.setSaslPrincipal( "ldap/" + this.bindHost + "@KEYCLOAK.ORG"); - ldapServer.setSaslRealms(new ArrayList()); return ldapServer; } @@ -143,6 +143,7 @@ public class LDAPEmbeddedServer { private void importLdif() throws Exception { Map map = new HashMap(); map.put("hostname", this.bindHost); + map.put("ldapSaslPrincipal", this.ldapSaslPrincipal); // For now, assume that LDIF file is on classpath InputStream is = getClass().getClassLoader().getResourceAsStream(ldifFile); diff --git a/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif index acdd570a68..fd9936cfa9 100644 --- a/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif +++ b/testsuite/integration/src/main/resources/kerberos/users-kerberos.ldif @@ -32,7 +32,7 @@ cn: LDAP sn: Service uid: ldap userPassword: randall -krb5PrincipalName: ldap/${hostname}@KEYCLOAK.ORG +krb5PrincipalName: ${ldapSaslPrincipal} krb5KeyVersionNumber: 0 dn: uid=HTTP,ou=People,dc=keycloak,dc=org From 7d2d3230d20e56e4e0d49bc28bb2f4019094cd47 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 13 Mar 2015 19:34:40 +0100 Subject: [PATCH 4/7] KEYCLOAK-1099 Test of identity providers and protocol mappers import --- .../keycloak/testsuite/model/ImportTest.java | 39 ++++++++++++++++--- .../src/test/resources/model/testrealm.json | 24 +++++++++--- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java index 506013b70b..dd32e7cef0 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java @@ -4,11 +4,14 @@ import org.junit.Assert; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; +import org.keycloak.constants.KerberosConstants; import org.keycloak.models.ApplicationModel; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; @@ -16,6 +19,9 @@ import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.RealmManager; @@ -197,12 +203,16 @@ public class ImportTest extends AbstractModelTest { Assert.assertEquals("localhost", smtpConfig.get("host")); Assert.assertEquals("3025", smtpConfig.get("port")); - // Test social config - //FIXME: KEYCLOAK-883 -// Map socialConfig = realm.getSocialConfig(); -// Assert.assertTrue(socialConfig.size() == 2); -// Assert.assertEquals("abc", socialConfig.get("google.key")); -// Assert.assertEquals("def", socialConfig.get("google.secret")); + // Test identity providers + List identityProviders = realm.getIdentityProviders(); + Assert.assertEquals(1, identityProviders.size()); + IdentityProviderModel google = identityProviders.get(0); + Assert.assertEquals("google1", google.getId()); + Assert.assertEquals("google", google.getProviderId()); + Assert.assertEquals("Google", google.getName()); + Assert.assertTrue(google.isEnabled()); + Assert.assertEquals("googleId", google.getConfig().get("clientId")); + Assert.assertEquals("googleSecret", google.getConfig().get("clientSecret")); // Test federation providers List fedProviders = realm.getUserFederationProviders(); @@ -216,6 +226,23 @@ public class ImportTest extends AbstractModelTest { // Assert that federation link wasn't created during import UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, "dummy"); Assert.assertNull(factory.getInstance(session, null).getUserByUsername(realm, "wburke")); + + // Test protocol mappers. Default application has all the builtin protocol mappers. OtherApp just gss credential + Assert.assertNotNull(application.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "username")); + Assert.assertNotNull(application.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "email")); + Assert.assertNotNull(application.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "given name")); + Assert.assertNull(application.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME)); + + Assert.assertEquals(1, otherApp.getProtocolMappers().size()); + Assert.assertNull(otherApp.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "username")); + ProtocolMapperModel gssCredentialMapper = otherApp.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME); + Assert.assertEquals(KerberosConstants.GSS_DELEGATION_CREDENTIAL_DISPLAY_NAME, gssCredentialMapper.getName()); + Assert.assertEquals( OIDCLoginProtocol.LOGIN_PROTOCOL, gssCredentialMapper.getProtocol()); + Assert.assertEquals(UserSessionNoteMapper.PROVIDER_ID, gssCredentialMapper.getProtocolMapper()); + String includeInAccessToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN); + String includeInIdToken = gssCredentialMapper.getConfig().get(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN); + Assert.assertTrue(includeInAccessToken.equalsIgnoreCase("true")); + Assert.assertTrue(includeInIdToken == null || Boolean.parseBoolean(includeInIdToken) == false); } @Test diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json index 8d36a576fc..f43c7fc610 100755 --- a/testsuite/integration/src/test/resources/model/testrealm.json +++ b/testsuite/integration/src/test/resources/model/testrealm.json @@ -15,12 +15,12 @@ "identityProviders" : [ { "providerId" : "google", - "id" : "google", + "id" : "google1", "name" : "Google", "enabled": true, "config": { - "clientId": "clientId", - "clientSecret": "clientSecret" + "clientId": "googleId", + "clientSecret": "googleSecret" } } ], @@ -111,9 +111,23 @@ }, { "name": "OtherApp", - "enabled": true + "enabled": true, + "protocolMappers" : [ + { + "name" : "gss delegation credential", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : true, + "consentText" : "gss delegation credential", + "config" : { + "user.session.note" : "gss_delegation_credential", + "access.token.claim" : "true", + "claim.name" : "gss_delegation_credential", + "Claim JSON Type" : "String" + } + } + ] } - ], "oauthClients" : [ { From 9b74393add8a84815dff0182f819da568d71c4a2 Mon Sep 17 00:00:00 2001 From: mposolda Date: Fri, 13 Mar 2015 19:55:54 +0100 Subject: [PATCH 5/7] KEYCLOAK-1102 Used just one ResteasyClient in admin client --- .../src/main/java/org/keycloak/admin/client/Keycloak.java | 5 +++-- .../org/keycloak/admin/client/token/TokenManager.java | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java index 1488d38f3c..20914e5de0 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java @@ -20,9 +20,10 @@ public class Keycloak { private Keycloak(String serverUrl, String realm, String username, String password, String clientId, String clientSecret){ config = new Config(serverUrl, realm, username, password, clientId, clientSecret); - tokenManager = new TokenManager(config); - client = new ResteasyClientBuilder().build(); + + tokenManager = new TokenManager(config, client); + target = client.target(config.getServerUrl()); target.register(new BearerAuthFilter(tokenManager.getAccessTokenString())); diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java index 3e3d923ea2..f5a796dd24 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java @@ -18,10 +18,12 @@ public class TokenManager { private AccessTokenResponse currentToken; private Date expirationTime; - private Config config; + private final Config config; + private final ResteasyClient client; - public TokenManager(Config config){ + public TokenManager(Config config, ResteasyClient client){ this.config = config; + this.client = client; } public String getAccessTokenString(){ @@ -38,7 +40,6 @@ public class TokenManager { } public AccessTokenResponse grantToken(){ - ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target(config.getServerUrl()); Form form = new Form() @@ -60,7 +61,6 @@ public class TokenManager { } public AccessTokenResponse refreshToken(){ - ResteasyClient client = new ResteasyClientBuilder().build(); ResteasyWebTarget target = client.target(config.getServerUrl()); Form form = new Form() From 144ee2c56dfd7ed50709dc335df21db93ce2cde4 Mon Sep 17 00:00:00 2001 From: Michael Gerber Date: Sat, 21 Feb 2015 17:36:47 +0100 Subject: [PATCH 6/7] add localization support to realm (cherry picked from commit b00764b) add localization support to realm (cherry picked from commit b00764b) add localization support to realm (cherry picked from commit b00764b) admin gui for localization support (cherry picked from commit c157cc3) added locale support to ClassLoaderTheme and FolderTheme (cherry picked from commit 764960a) rename localizatin to internationalization (cherry picked from commit 74a0658) fallback if no message can be found (cherry picked from commit 2d54e6f) fallback if no message can be found (cherry picked from commit 2d54e6f) add HttpHeaders (cherry picked from commit be921c8) add HttpHeaders (cherry picked from commit be921c8) NPE fix (cherry picked from commit 148158b) improve locale helper (cherry picked from commit 57a805a) write cookie fix npe (cherry picked from commit 33398b3) dropdown to choose language (cherry picked from commit 9e2eb60) added dropdown to choose locale to the account page. fixed the path of the cookie. (cherry picked from commit 871469a) remove debug info (cherry picked from commit c3ea6b1) added dropdown to change locale for user. (cherry picked from commit 1575bea) add i18n for emails (cherry picked from commit b6280e9) i18n for emails (cherry picked from commit 6118c51) set default locale to english (cherry picked from commit 386447b) change locale type form String to Locale (cherry picked from commit 8dcc595) change file encoding (cherry picked from commit 2cce96d) added parameters to setSuccess, setWarning, setError (cherry picked from commit 64ea438) i18n for error messages (cherry picked from commit e75d5ef) i18n for error message in login (cherry picked from commit 8b60c96) add i18n for messages in account pages (cherry picked from commit 9849d94) fix unit test (cherry picked from commit a70a1c7) fix unit test (cherry picked from commit 008744b) locale localization :) (cherry picked from commit f3a2144) locale localization :) (cherry picked from commit 21f6384) messages refactoring (cherry picked from commit 08f1ae7) messages refactoring (cherry picked from commit 3b4b53a) messages refactoring (cherry picked from commit 69146d4) messages refactoring (cherry picked from commit cd3db1d) messages refactoring (cherry picked from commit 715583f) fix unit tests after message refactoring (cherry picked from commit a82c9ff) fix unit tests after message refactoring (cherry picked from commit 5662d83) fix unit tests after message refactoring (cherry picked from commit 1188de6) fix unit tests after message refactoring (cherry picked from commit 90ded07) German translations (cherry picked from commit 9822b99) removed newliines (cherry picked from commit 34ca1d8) removed java 8 dependency (cherry picked from commit 3a23f4f) typo (cherry picked from commit a818122) typo (cherry picked from commit a818122) forgotten properties unit test fixes unit test fixes fixes after rebase add localization support to realm (cherry picked from commit b00764b) add localization support to realm (cherry picked from commit b00764b) add localization support to realm (cherry picked from commit b00764b) admin gui for localization support (cherry picked from commit c157cc3) added locale support to ClassLoaderTheme and FolderTheme (cherry picked from commit 764960a) rename localizatin to internationalization (cherry picked from commit 74a0658) fallback if no message can be found (cherry picked from commit 2d54e6f) fallback if no message can be found (cherry picked from commit 2d54e6f) add HttpHeaders (cherry picked from commit be921c8) add HttpHeaders (cherry picked from commit be921c8) NPE fix (cherry picked from commit 148158b) improve locale helper (cherry picked from commit 57a805a) write cookie fix npe (cherry picked from commit 33398b3) dropdown to choose language (cherry picked from commit 9e2eb60) added dropdown to choose locale to the account page. fixed the path of the cookie. (cherry picked from commit 871469a) remove debug info (cherry picked from commit c3ea6b1) added dropdown to change locale for user. (cherry picked from commit 1575bea) add i18n for emails (cherry picked from commit b6280e9) i18n for emails (cherry picked from commit 6118c51) set default locale to english (cherry picked from commit 386447b) change locale type form String to Locale (cherry picked from commit 8dcc595) change file encoding (cherry picked from commit 2cce96d) added parameters to setSuccess, setWarning, setError (cherry picked from commit 64ea438) i18n for error messages (cherry picked from commit e75d5ef) i18n for error message in login (cherry picked from commit 8b60c96) add i18n for messages in account pages (cherry picked from commit 9849d94) fix unit test (cherry picked from commit a70a1c7) fix unit test (cherry picked from commit 008744b) locale localization :) (cherry picked from commit f3a2144) locale localization :) (cherry picked from commit 21f6384) messages refactoring (cherry picked from commit 08f1ae7) messages refactoring (cherry picked from commit 3b4b53a) messages refactoring (cherry picked from commit 69146d4) messages refactoring (cherry picked from commit cd3db1d) messages refactoring (cherry picked from commit 715583f) fix unit tests after message refactoring (cherry picked from commit a82c9ff) fix unit tests after message refactoring (cherry picked from commit 5662d83) fix unit tests after message refactoring (cherry picked from commit 1188de6) fix unit tests after message refactoring (cherry picked from commit 90ded07) German translations (cherry picked from commit 9822b99) removed newliines (cherry picked from commit 34ca1d8) removed java 8 dependency (cherry picked from commit 3a23f4f) typo (cherry picked from commit a818122) typo (cherry picked from commit a818122) forgotten properties unit test fixes unit test fixes fixes after rebase fixes after rebase fixes after rebase fixes after rebase --- .../META-INF/jpa-changelog-1.2.0.Beta1.xml | 9 ++ .../idm/RealmRepresentation.java | 37 ++++- .../org/keycloak/account/AccountProvider.java | 10 +- .../freemarker/FreeMarkerAccountProvider.java | 68 ++++---- .../account/freemarker/model/RealmBean.java | 49 ++++++ .../account/freemarker/model/UrlBean.java | 4 + .../freemarker/ExtendingThemeManager.java | 13 +- .../org/keycloak/freemarker/LocaleHelper.java | 113 +++++++++++++ .../java/org/keycloak/freemarker/Theme.java | 3 +- .../freemarker/beans/TextFormatterBean.java | 19 +++ .../keycloak/freemarke/LocaleHelperTest.java | 24 +++ forms/common-themes/pom.xml | 5 + .../org/keycloak/theme/ClassLoaderTheme.java | 15 +- .../java/org/keycloak/theme/FolderTheme.java | 10 +- .../account/base/messages/messages.properties | 48 ------ .../base/messages/messages_de.properties | 57 +++++++ .../base/messages/messages_en.properties | 57 +++++++ .../resources/theme/account/base/sessions.ftl | 2 +- .../resources/theme/account/base/template.ftl | 22 +++ .../base/resources/js/controllers/realm.js | 12 ++ .../partials/realm-theme-settings.html | 27 ++++ .../base/resources/partials/user-detail.html | 14 +- .../email/keycloak/email-verification.ftl | 6 +- .../email/keycloak/event-login_error.ftl | 2 +- .../email/keycloak/event-remove_totp.ftl | 2 +- .../email/keycloak/event-update_password.ftl | 2 +- .../email/keycloak/event-update_totp.ftl | 2 +- .../keycloak/messages/messages.properties | 2 - .../keycloak/messages/messages_de.properties | 12 ++ .../keycloak/messages/messages_en.properties | 12 ++ .../theme/email/keycloak/password-reset.ftl | 6 +- .../theme/login/base/login-config-totp.ftl | 2 +- .../theme/login/base/login-oauth-grant.ftl | 6 +- .../theme/login/base/login-reset-password.ftl | 6 +- .../resources/theme/login/base/login-totp.ftl | 8 +- .../login/base/login-update-password.ftl | 6 +- .../theme/login/base/login-update-profile.ftl | 2 +- .../theme/login/base/login-verify-email.ftl | 6 +- .../main/resources/theme/login/base/login.ftl | 20 +-- .../login/base/messages/messages.properties | 126 --------------- .../base/messages/messages_de.properties | 148 ++++++++++++++++++ .../base/messages/messages_en.properties | 146 +++++++++++++++++ .../resources/theme/login/base/register.ftl | 6 +- .../resources/theme/login/base/template.ftl | 22 +++ .../freemarker/FreeMarkerEmailProvider.java | 26 ++- .../keycloak/login/LoginFormsProvider.java | 9 +- .../FreeMarkerLoginFormsProvider.java | 65 +++++--- .../login/freemarker/model/RealmBean.java | 10 ++ .../login/freemarker/model/UrlBean.java | 4 + .../org/keycloak/models/ModelException.java | 14 ++ .../org/keycloak/models/PasswordPolicy.java | 58 +++++-- .../java/org/keycloak/models/RealmModel.java | 7 + .../models/UserFederationManager.java | 4 +- .../java/org/keycloak/models/UserModel.java | 1 + .../keycloak/models/entities/RealmEntity.java | 28 ++++ .../models/utils/ModelToRepresentation.java | 4 + .../models/utils/RepresentationToModel.java | 20 +++ .../keycloak/models/PasswordPolicyTest.java | 32 ++-- .../models/file/adapter/RealmAdapter.java | 30 ++++ .../keycloak/models/cache/RealmAdapter.java | 35 +++++ .../models/cache/entities/CachedRealm.java | 19 +++ .../org/keycloak/models/jpa/RealmAdapter.java | 32 ++++ .../models/jpa/entities/RealmEntity.java | 35 +++++ .../mongo/keycloak/adapters/RealmAdapter.java | 35 +++++ .../keycloak/protocol/saml/SamlProtocol.java | 16 +- .../keycloak/protocol/saml/SamlService.java | 47 +++--- .../org/keycloak/protocol/LoginProtocol.java | 3 + .../protocol/oidc/OIDCLoginProtocol.java | 15 +- .../oidc/OIDCLoginProtocolService.java | 5 +- .../keycloak/protocol/oidc/TokenManager.java | 9 +- .../oidc/endpoints/AuthorizationEndpoint.java | 37 ++--- .../oidc/endpoints/LogoutEndpoint.java | 5 +- .../oidc/endpoints/TokenEndpoint.java | 2 +- .../oidc/endpoints/ValidateTokenEndpoint.java | 10 +- .../keycloak/services/ErrorPageException.java | 9 +- .../services/managers/AppAuthManager.java | 2 +- .../managers/AuthenticationManager.java | 25 +-- .../managers/HttpAuthenticationManager.java | 8 +- .../keycloak/services/messages/Messages.java | 148 ++++++++++++++---- .../services/resources/AccountService.java | 33 ++-- .../resources/IdentityBrokerService.java | 59 ++++--- .../resources/LoginActionsService.java | 141 +++++++++-------- .../services/resources/flows/Flows.java | 9 +- .../services/resources/flows/Urls.java | 4 + .../services/validation/Validation.java | 2 +- .../testsuite/account/AccountTest.java | 6 +- .../RequiredActionEmailVerificationTest.java | 2 +- .../RequiredActionUpdateProfileTest.java | 8 +- .../broker/AbstractIdentityProviderTest.java | 2 +- .../FederationProvidersIntegrationTest.java | 4 +- .../keycloak/testsuite/forms/LoginTest.java | 4 +- .../testsuite/forms/RegisterTest.java | 16 +- .../testsuite/forms/ResetPasswordTest.java | 2 +- .../testsuite/oauth/OAuthRedirectUriTest.java | 10 +- .../keycloak/testsuite/pages/LoginPage.java | 2 +- 95 files changed, 1680 insertions(+), 601 deletions(-) create mode 100755 forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java create mode 100644 forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java create mode 100644 forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java create mode 100644 forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java delete mode 100755 forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties create mode 100644 forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties create mode 100755 forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties delete mode 100755 forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages.properties create mode 100644 forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties create mode 100755 forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties delete mode 100755 forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties create mode 100644 forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties create mode 100755 forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml index a723490bb6..b545aa3051 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml @@ -73,6 +73,12 @@ + + + + + + @@ -88,11 +94,14 @@ + + + diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 023172f9ae..532807e53d 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -1,10 +1,6 @@ package org.keycloak.representations.idm; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * @author Bill Burke @@ -68,6 +64,10 @@ public class RealmRepresentation { private List identityProviders; private List protocolMappers; private Boolean identityFederationEnabled; + protected Boolean internationalizationEnabled; + protected Set supportedLocales; + protected String defaultLocale; + public String getId() { return id; @@ -513,4 +513,31 @@ public class RealmRepresentation { public void setProtocolMappers(List protocolMappers) { this.protocolMappers = protocolMappers; } + + public Boolean isInternationalizationEnabled() { + return internationalizationEnabled; + } + + public void setInternationalizationEnabled(Boolean internationalizationEnabled) { + this.internationalizationEnabled = internationalizationEnabled; + } + + public Set getSupportedLocales() { + if(supportedLocales == null){ + supportedLocales = new HashSet(); + } + return supportedLocales; + } + + public void setSupportedLocales(Set supportedLocales) { + this.supportedLocales = supportedLocales; + } + + public String getDefaultLocale() { + return defaultLocale; + } + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } } diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java index 7677a2091e..bc6920b5fa 100755 --- a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java +++ b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java @@ -1,11 +1,13 @@ package org.keycloak.account; +import org.apache.http.client.methods.HttpHead; import org.keycloak.events.Event; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.provider.Provider; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -18,13 +20,15 @@ public interface AccountProvider extends Provider { AccountProvider setUriInfo(UriInfo uriInfo); + AccountProvider setHttpHeaders(HttpHeaders httpHeaders); + Response createResponse(AccountPages page); - AccountProvider setError(String message); + AccountProvider setError(String message, Object ... parameters); - AccountProvider setSuccess(String message); + AccountProvider setSuccess(String message, Object ... parameters); - AccountProvider setWarning(String message); + AccountProvider setWarning(String message, Object ... parameters); AccountProvider setUser(UserModel user); diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java index 368b4591e3..881606f47f 100755 --- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java @@ -3,38 +3,21 @@ package org.keycloak.account.freemarker; import org.jboss.logging.Logger; import org.keycloak.account.AccountPages; import org.keycloak.account.AccountProvider; -import org.keycloak.account.freemarker.model.AccountBean; -import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean; -import org.keycloak.account.freemarker.model.FeaturesBean; -import org.keycloak.account.freemarker.model.LogBean; -import org.keycloak.account.freemarker.model.MessageBean; -import org.keycloak.account.freemarker.model.PasswordBean; -import org.keycloak.account.freemarker.model.ReferrerBean; -import org.keycloak.account.freemarker.model.SessionsBean; -import org.keycloak.account.freemarker.model.TotpBean; -import org.keycloak.account.freemarker.model.UrlBean; +import org.keycloak.account.freemarker.model.*; import org.keycloak.events.Event; -import org.keycloak.freemarker.BrowserSecurityHeaderSetup; -import org.keycloak.freemarker.FreeMarkerException; -import org.keycloak.freemarker.FreeMarkerUtil; -import org.keycloak.freemarker.Theme; -import org.keycloak.freemarker.ThemeProvider; +import org.keycloak.freemarker.*; +import org.keycloak.freemarker.beans.TextFormatterBean; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.services.resources.flows.Urls; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.core.*; import java.io.IOException; import java.net.URI; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.text.MessageFormat; +import java.util.*; /** * @author Stian Thorgersen @@ -57,12 +40,14 @@ public class FreeMarkerAccountProvider implements AccountProvider { private boolean passwordSet; private KeycloakSession session; private FreeMarkerUtil freeMarker; + private HttpHeaders headers; public static enum MessageType {SUCCESS, WARNING, ERROR} private UriInfo uriInfo; private String message; + private Object[] parameters; private MessageType messageType; public FreeMarkerAccountProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { @@ -75,6 +60,12 @@ public class FreeMarkerAccountProvider implements AccountProvider { return this; } + @Override + public AccountProvider setHttpHeaders(HttpHeaders httpHeaders) { + this.headers = httpHeaders; + return this; + } + @Override public Response createResponse(AccountPages page) { Map attributes = new HashMap(); @@ -94,9 +85,14 @@ public class FreeMarkerAccountProvider implements AccountProvider { logger.warn("Failed to load properties", e); } + Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, headers); + if(locale != null){ + attributes.put("locale", locale); + attributes.put("formatter", new TextFormatterBean(locale)); + } Properties messages; try { - messages = theme.getMessages(); + messages = theme.getMessages(locale); attributes.put("rb", messages); } catch (IOException e) { logger.warn("Failed to load messages", e); @@ -115,13 +111,23 @@ public class FreeMarkerAccountProvider implements AccountProvider { } if (message != null) { - attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType)); + String formattedMessage; + if(messages.containsKey(message)){ + formattedMessage = new MessageFormat(messages.getProperty(message).replace("'","''"),locale).format(parameters); + }else{ + formattedMessage = message; + } + attributes.put("message", new MessageBean(formattedMessage, messageType)); } if (referrer != null) { attributes.put("referrer", new ReferrerBean(referrer)); } + if(realm != null){ + attributes.put("realm", new RealmBean(realm)); + } + attributes.put("url", new UrlBean(realm, theme, baseUri, baseQueryUri, uriInfo.getRequestUri(), stateChecker)); attributes.put("features", new FeaturesBean(identityProviderEnabled, eventsEnabled, passwordUpdateSupported)); @@ -150,6 +156,7 @@ public class FreeMarkerAccountProvider implements AccountProvider { String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme); Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result); BrowserSecurityHeaderSetup.headers(builder, realm); + LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri,realm.getName())); return builder.build(); } catch (FreeMarkerException e) { logger.error("Failed to process template", e); @@ -163,22 +170,25 @@ public class FreeMarkerAccountProvider implements AccountProvider { } @Override - public AccountProvider setError(String message) { + public AccountProvider setError(String message, Object ... parameters) { this.message = message; + this.parameters = parameters; this.messageType = MessageType.ERROR; return this; } @Override - public AccountProvider setSuccess(String message) { + public AccountProvider setSuccess(String message, Object ... parameters) { this.message = message; + this.parameters = parameters; this.messageType = MessageType.SUCCESS; return this; } @Override - public AccountProvider setWarning(String message) { + public AccountProvider setWarning(String message, Object ... parameters) { this.message = message; + this.parameters = parameters; this.messageType = MessageType.WARNING; return this; } diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java new file mode 100755 index 0000000000..b0a5eb4959 --- /dev/null +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java @@ -0,0 +1,49 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.account.freemarker.model; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredCredentialModel; +import org.keycloak.representations.idm.CredentialRepresentation; + +import java.util.Set; + +/** + * @author Michael Gerber + */ +public class RealmBean { + + private RealmModel realm; + + public RealmBean(RealmModel realmModel) { + realm = realmModel; + } + + public boolean isInternationalizationEnabled() { + return realm.isInternationalizationEnabled(); + } + + public Set getSupportedLocales(){ + return realm.getSupportedLocales(); + } + +} diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java index de7e432d6a..a357aadd78 100755 --- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java +++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java @@ -59,6 +59,10 @@ public class UrlBean { return Urls.accountSessionsLogoutPage(baseQueryURI, realm, stateChecker).toString(); } + public String getLocaleCookiePath(){ + return Urls.localeCookiePath(baseURI, realm); + } + public String getTotpRemoveUrl() { return Urls.accountTotpRemove(baseQueryURI, realm, stateChecker).toString(); } diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java index 533b63120d..1fefce1921 100644 --- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java @@ -7,14 +7,7 @@ import org.keycloak.models.KeycloakSession; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.Properties; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -228,11 +221,11 @@ public class ExtendingThemeManager implements ThemeProvider { } @Override - public Properties getMessages() throws IOException { + public Properties getMessages(Locale locale) throws IOException { Properties messages = new Properties(); ListIterator itr = themes.listIterator(themes.size()); while (itr.hasPrevious()) { - Properties m = itr.previous().getMessages(); + Properties m = itr.previous().getMessages(locale); if (m != null) { messages.putAll(m); } diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java new file mode 100644 index 0000000000..e153fcf511 --- /dev/null +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java @@ -0,0 +1,113 @@ +package org.keycloak.freemarker; + +import org.jboss.logging.Logger; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.*; + +/** + * @author Michael Gerber + */ +public class LocaleHelper { + public final static String LOCALE_COOKIE = "KEYCLOAK_LOCALE"; + public static final String LOCALE_PARAM = "ui_locale"; + + private final static Logger LOGGER = Logger.getLogger(LocaleHelper.class); + + public static Locale getLocale(RealmModel realm, UserModel user) { + return getLocale(realm, user, null, null); + } + + public static Locale getLocale(RealmModel realm, UserModel user, UriInfo uriInfo, HttpHeaders httpHeaders) { + if(!realm.isInternationalizationEnabled()){ + return Locale.ENGLISH; + } + + //1. Locale cookie + if(httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)){ + String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue(); + Locale locale = findLocale(localeString, realm.getSupportedLocales()); + if(locale != null){ + if(user != null){ + user.setAttribute(UserModel.LOCALE, locale.toLanguageTag()); + } + return locale; + }else{ + LOGGER.infof("Locale %s is not supported.", localeString); + } + } + + //2. User profile + if(user != null && user.getAttributes().containsKey(UserModel.LOCALE)){ + String localeString = user.getAttribute(UserModel.LOCALE); + Locale locale = findLocale(localeString, realm.getSupportedLocales()); + if(locale != null){ + + return locale; + }else{ + LOGGER.infof("Locale %s is not supported.", localeString); + } + } + + //3. ui_locales query parameter + if(uriInfo != null && uriInfo.getQueryParameters().containsKey(LOCALE_PARAM)){ + String localeString = uriInfo.getQueryParameters().getFirst(LOCALE_PARAM); + Locale locale = findLocale(localeString, realm.getSupportedLocales()); + if(locale != null){ + return locale; + }else{ + LOGGER.infof("Locale %s is not supported.", localeString); + } + } + + //4. Accept-Language http header + if(httpHeaders !=null && httpHeaders.getAcceptableLanguages() != null && !httpHeaders.getAcceptableLanguages().isEmpty()){ + for(Locale l : httpHeaders.getAcceptableLanguages()){ + String localeString = l.toLanguageTag(); + Locale locale = findLocale(localeString, realm.getSupportedLocales()); + if(locale != null){ + return locale; + }else{ + LOGGER.infof("Locale %s is not supported.", localeString); + } + } + } + + //5. Default realm locale + if(realm.getDefaultLocale() != null){ + return Locale.forLanguageTag(realm.getDefaultLocale()); + } + + return Locale.ENGLISH; + } + + public static void updateLocaleCookie(Response.ResponseBuilder builder, Locale locale, RealmModel realm, UriInfo uriInfo, String path) { + if (locale == null) { + return; + } + boolean secure = realm.getSslRequired().isRequired(uriInfo.getRequestUri().getHost()); + builder.cookie(new NewCookie(LocaleHelper.LOCALE_COOKIE, locale.toLanguageTag(), path, null, null, 31536000, secure)); + } + + public static Locale findLocale(String localeString, Set supportedLocales) { + Locale result = null; + Locale search = Locale.forLanguageTag(localeString); + for(String languageTag : supportedLocales) { + Locale locale = Locale.forLanguageTag(languageTag); + if(locale.getLanguage().equals(search.getLanguage())){ + if(locale.getCountry().equals("") && result == null){ + result = locale; + } + if(locale.getCountry().equals(search.getCountry())){ + return locale; + } + } + } + return result; + } +} diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java index dff92ee861..6a12a49f00 100644 --- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java @@ -3,6 +3,7 @@ package org.keycloak.freemarker; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.Locale; import java.util.Properties; /** @@ -28,7 +29,7 @@ public interface Theme { public InputStream getResourceAsStream(String path) throws IOException; - public Properties getMessages() throws IOException; + public Properties getMessages(Locale locale) throws IOException; public Properties getProperties() throws IOException; diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java new file mode 100644 index 0000000000..5243d56cae --- /dev/null +++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java @@ -0,0 +1,19 @@ +package org.keycloak.freemarker.beans; + +import java.text.MessageFormat; +import java.util.Locale; + +/** + * @author Michael Gerber + */ +public class TextFormatterBean { + private Locale locale; + + public TextFormatterBean(Locale locale) { + this.locale = locale; + } + + public String format(String pattern, Object ... parameters){ + return new MessageFormat(pattern.replace("'","''"),locale).format(parameters); + } +} diff --git a/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java b/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java new file mode 100644 index 0000000000..01bb9986b2 --- /dev/null +++ b/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java @@ -0,0 +1,24 @@ +package org.keycloak.freemarke; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.freemarker.LocaleHelper; + +import java.util.Arrays; +import java.util.HashSet; + +/** + * @author Michael Gerber + */ +public class LocaleHelperTest { + @Test + public void findLocaleTest(){ + Assert.assertEquals("de", LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag()); + Assert.assertEquals("en", LocaleHelper.findLocale("en", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag()); + Assert.assertEquals("de", LocaleHelper.findLocale("de-CH", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag()); + Assert.assertEquals("de-CH", LocaleHelper.findLocale("de-CH", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag()); + Assert.assertEquals("de-DE", LocaleHelper.findLocale("de-DE", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag()); + Assert.assertEquals("de", LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag()); + Assert.assertNull(LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de-CH","de-DE")))); + } +} diff --git a/forms/common-themes/pom.xml b/forms/common-themes/pom.xml index 8ad524fd80..04ea6bc30e 100755 --- a/forms/common-themes/pom.xml +++ b/forms/common-themes/pom.xml @@ -42,6 +42,11 @@ resteasy-jaxrs provided
+ + org.jboss.logging + jboss-logging + provided +
diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java index 1a793eee7c..c95edd2ea7 100755 --- a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java +++ b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java @@ -2,10 +2,13 @@ package org.keycloak.theme; import org.keycloak.freemarker.Theme; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.Locale; import java.util.Properties; +import java.util.ResourceBundle; /** * @author Stian Thorgersen @@ -26,7 +29,7 @@ public class ClassLoaderTheme implements Theme { private String resourceRoot; - private String messages; + private String messageRoot; private Properties properties; @@ -43,7 +46,7 @@ public class ClassLoaderTheme implements Theme { this.templateRoot = themeRoot; this.resourceRoot = themeRoot + "resources/"; - this.messages = themeRoot + "messages/messages.properties"; + this.messageRoot = themeRoot + "messages/"; this.properties = new Properties(); URL p = classLoader.getResource(themeRoot + "theme.properties"); @@ -102,9 +105,13 @@ public class ClassLoaderTheme implements Theme { } @Override - public Properties getMessages() throws IOException { + public Properties getMessages(Locale locale) throws IOException { + if(locale == null){ + return null; + } Properties m = new Properties(); - URL url = classLoader.getResource(this.messages); + + URL url = classLoader.getResource(this.messageRoot + "messages_" + locale.toString() + ".properties"); if (url != null) { m.load(url.openStream()); } diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java index aa2312e707..0edc92ee5f 100644 --- a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java +++ b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java @@ -7,6 +7,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.Locale; import java.util.Properties; /** @@ -81,9 +82,14 @@ public class FolderTheme implements Theme { } @Override - public Properties getMessages() throws IOException { + public Properties getMessages(Locale locale) throws IOException { + if(locale == null){ + return null; + } + Properties m = new Properties(); - File file = new File(themeDir, "messages" + File.separator + "messages.properties"); + + File file = new File(themeDir, "messages" + File.separator + "messages_" + locale.toString() + ".properties"); if (file.isFile()) { m.load(new FileInputStream(file)); } diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties deleted file mode 100755 index 9ada6842b3..0000000000 --- a/forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties +++ /dev/null @@ -1,48 +0,0 @@ -authenticatorCode=One-time code -email=Email -errorHeader=Error! -firstName=First name -lastName=Last name -password=Password -passwordConfirm=Confirmation -passwordNew=New Password -successHeader=Success! -username=Username -street=Street -locality=City or Locality -region=State, Province, or Region -postal_code=Zip or Postal code -country=Country - -missingFirstName=Please specify first name -invalidEmail=Invalid email address -missingLastName=Please specify last name -missingEmail=Please specify email -missingPassword=Please specify password. -notMatchPassword=Passwords don't match - -missingTotp=Please specify authenticator code -invalidPasswordExisting=Invalid existing password -invalidPasswordConfirm=Password confirmation doesn't match -invalidTotp=Invalid authenticator code -readOnlyUser=You can't update your account as it is read only -readOnlyPassword=You can't update your password as your account is read only - -successTotp=Mobile authenticator configured. -successTotpRemoved=Mobile authenticator removed. - -accountUpdated=Your account has been updated -accountPasswordUpdated=Your password has been updated - -missingIdentityProvider=Identity provider not specified -invalidFederatedIdentityAction=Invalid or missing action -identityProviderNotFound=Specified identity provider not found -federatedIdentityLinkNotActive=This identity is not active anymore -federatedIdentityRemovingLastProvider=You can't remove last federated identity as you don't have password -identityProviderRedirectError=Failed to redirect to identity provider -identityProviderRemoved=Identity provider removed successfully - -accountDisabled=Account is disabled, contact admin\ -accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later - -logOutAllSessions=Log out all sessions diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties new file mode 100644 index 0000000000..4d3e4b37bc --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties @@ -0,0 +1,57 @@ +authenticatorCode=One-time code +email=E-Mail +firstName=Vorname +lastName=Nachname +password=Passwort +passwordConfirm=Passwort bestätigung +passwordNew=Neues Passwort +username=Benutzernamen +street=Strasse +region=Staat, Provinz, Region +postal_code=PLZ +locality=Stadt oder Ortschaft +country=Land + +missingFirstNameMessage=Bitte geben Sie einen Vornamen ein. +missingEmailMessage=Bitte geben Sie eine E-Mail Adresse ein. +missingLastNameMessage=Bitte geben Sie einen Nachnamen ein. +missingPasswordMessage=Bitte geben Sie ein Passwort ein. +notMatchPasswordMessage=Passwörter sind nicht identisch. + +missingTotpMessage=Bitte geben Sie den One-time Code ein. +invalidPasswordExistingMessage=Das aktuelle Passwort is ungültig. +invalidPasswordConfirmMessage=Die Passwort bestätigung ist nicht identisch. +invalidTotpMessage=Ungültiger One-time Code. +invalidEmailMessage=Ungültige E-Mail Adresse. + +readOnlyUserMessage=Sie können dieses Benutzerkonto nicht ändern, da es schreibgeschützt ist. +readOnlyPasswordMessage=Sie können dieses Passwort nicht ändern, da es schreibgeschützt ist. + +successTotpMessage=Mobile Authentifizierung eingerichtet. +successTotpRemovedMessage=Mobile Authentifizierung entfernt. + +accountUpdatedMessage=Ihr Benutzerkonto wurde aktualisiert. +accountPasswordUpdatedMessage=Ihr Passwort wurde aktualisiert. + +missingIdentityProviderMessage=Identity Provider nicht angegeben. +invalidFederatedIdentityActionMessage=Ungültige oder fehlende Aktion. +identityProviderNotFoundMessage=Angegebener Identity Provider nicht gefunden. +federatedIdentityLinkNotActiveMessage=Diese Identität ist nicht mehr aktiv. +federatedIdentityRemovingLastProviderMessage=Sie können den letzen Eintrag nicht enfernen, da Sie kein Passwort haben. +identityProviderRedirectErrorMessage=Fehler bei der Weiterleitung zum Identity Proivder. +identityProviderRemovedMessage=Identity Provider erfolgreich entfernt. + +accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin. + +doLogOutAllSessions=Alle Sessionen abmelden + +accountTemporarilyDisabledMessage=Benutzerkonto ist temporär gespert, bitte kontaktieren Sie den Admin oder versuchen Sie es später nocheinmal. +invalidPasswordMinLengthMessage=Ungültiges Passwort: minimum länge {0}. +invalidPasswordMinDigitsMessage=Ungültiges Passwort: muss mindestens {0} Zahl(en) beinhalten. +invalidPasswordMinLowerCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten. +invalidPasswordMinUpperCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten. +invalidPasswordMinSpecialCharsMessage=Ungültiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten. +invalidPasswordNotUsernameMessage=Ungültiges Passwort\: darf nicht gleich sein wie Benutzername. + +locale_de=Deutsch +locale_en=Englisch diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties new file mode 100755 index 0000000000..41072c8fd8 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties @@ -0,0 +1,57 @@ +authenticatorCode=One-time code +email=Email +firstName=First name +lastName=Last name +password=Password +passwordConfirm=Confirmation +passwordNew=New Password +username=Username +street=Street +locality=City or Locality +region=State, Province, or Region +postal_code=Zip or Postal code +country=Country + +missingFirstNameMessage=Please specify first name. +invalidEmailMessage=Invalid email address. +missingLastNameMessage=Please specify last name. +missingEmailMessage=Please specify email. +missingPasswordMessage=Please specify password. +notMatchPasswordMessage=Passwords don't match. + +missingTotpMessage=Please specify authenticator code +invalidPasswordExistingMessage=Invalid existing password +invalidPasswordConfirmMessage=Password confirmation doesn't match +invalidTotpMessage=Invalid authenticator code + +readOnlyUserMessage=You can't update your account as it is read only +readOnlyPasswordMessage=You can't update your password as your account is read only + +successTotpMessage=Mobile authenticator configured. +successTotpRemovedMessage=Mobile authenticator removed. + +accountUpdatedMessage=Your account has been updated +accountPasswordUpdatedMessage=Your password has been updated + +missingIdentityProviderMessage=Identity provider not specified +invalidFederatedIdentityActionMessage=Invalid or missing action +identityProviderNotFoundMessage=Specified identity provider not found +federatedIdentityLinkNotActiveMessage=This identity is not active anymore +federatedIdentityRemovingLastProviderMessage=You can't remove last federated identity as you don't have password +identityProviderRedirectErrorMessage=Failed to redirect to identity provider +identityProviderRemovedMessage=Identity provider removed successfully + +accountDisabledMessage=Account is disabled, contact admin + +doLogOutAllSessions=Log out all sessions + +accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later +invalidPasswordMinLengthMessage=Invalid password: minimum length {0} +invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters +invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits +invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters +invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters +invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username + +locale_de=German +locale_en=English diff --git a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl index 85c7a16702..435f188f98 100755 --- a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl +++ b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl @@ -42,6 +42,6 @@ - ${rb.logOutAllSessions} + ${rb.doLogOutAllSessions} diff --git a/forms/common-themes/src/main/resources/theme/account/base/template.ftl b/forms/common-themes/src/main/resources/theme/account/base/template.ftl index 11dc877bf4..0d862c3835 100644 --- a/forms/common-themes/src/main/resources/theme/account/base/template.ftl +++ b/forms/common-themes/src/main/resources/theme/account/base/template.ftl @@ -15,6 +15,19 @@ + <#if realm.internationalizationEnabled> + + @@ -28,6 +41,15 @@ +
+ +
+ +
+
+
+ + +
+ +
+
+
+ +
+
+ +
+
+
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html index 59fa3b2e36..5007f12ac1 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html @@ -94,12 +94,24 @@
-
+
+ +
+
+ +
+
+
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl index 38d150fd5d..301212fff9 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl @@ -1,5 +1 @@ -Someone has created a Keycloak account with this email address. If this was you, click the link below to verify your email address: -${link} -This link will expire within ${linkExpiration} minutes. - -If you didn't create this account, just ignore this message. \ No newline at end of file +${formatter.format(rb.emailVerificationBody,link, linkExpiration)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl index c1227aa882..cd9d247d9d 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl @@ -1 +1 @@ -A failed login attempt was dettected to your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin. \ No newline at end of file +${formatter.format(rb.eventLoginErrorBody,event.date,event.ipAddress)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl index c62e174053..37ae2f73a3 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl @@ -1 +1 @@ -TOTP was removed from your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin. \ No newline at end of file +${formatter.format(rb.eventRemoveTotpBody,event.date, event.ipAddress)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl index 696a6e6181..2c88214097 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl @@ -1 +1 @@ -Your password was changed on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin. \ No newline at end of file +${formatter.format(rb.eventUpdatePasswordBody,event.date, event.ipAddress)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl index 531ae662be..b34a898086 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl @@ -1 +1 @@ -TOTP was updated for your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin. \ No newline at end of file +${formatter.format(rb.eventUpdateTotpBody,event.date, event.ipAddress)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages.properties b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages.properties deleted file mode 100755 index 3139aca340..0000000000 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages.properties +++ /dev/null @@ -1,2 +0,0 @@ -emailVerificationSubject=Verify email -passwordResetSubject=Reset password \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties new file mode 100644 index 0000000000..a5a85f9ecf --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties @@ -0,0 +1,12 @@ +emailVerificationSubject=E-Mail verifizieren +passwordResetSubject=Passwort zurückzusetzen +passwordResetBody=Jemand hat angeforder Ihr Keycloak Passwort zurückzusetzen. Falls das Sie waren, dann klicken Sie auf den folgenden Link um das Passwort zurückzusetzen.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie das Passwort nicht zurücksetzen möchten, dann können Sie diese E-Mail ignorieren. +emailVerificationBody=Jemand hat ein Keycloak Konto mit dieser E-Mail Adresse erstellt. Fall das Sie waren, dann klicken Sie auf den Link um die E-Mail Adresse zu verifizieren.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie dieses Konto nicht erstellt haben, dann können sie diese Nachricht ignorieren. +eventLoginErrorSubject=Fehlgeschlagene Anmeldung +eventLoginErrorBody=Jemand hat um {0} von {1} versucht sich mit ihrem Konto anzumelden. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin. +eventRemoveTotpSubject=TOTP Entfernt +eventRemoveTotpBody=TOTP wurde von ihrem Konto am {0} von {1} entfernt. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin. +eventUpdatePasswordSubject=Passwort Aktualisiert +eventUpdatePasswordBody=Ihr Passwort wurde am {0} von {1} geändert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin. +eventUpdateTotpSubject=TOTP Aktualisiert +eventUpdateTotpBody=TOTP wurde am {0} von {1} geändert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin. diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties new file mode 100755 index 0000000000..e64a00fb68 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties @@ -0,0 +1,12 @@ +emailVerificationSubject=Verify email +emailVerificationBody=Someone has created a Keycloak account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you didn't create this account, just ignore this message. +passwordResetSubject=Reset password +passwordResetBody=Someone just requested to change your Keycloak account's password. If this was you, click on the link below to set a new password\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you don't want to reset your password, just ignore this message and nothing will be changed. +eventLoginErrorSubject=Login error +eventLoginErrorBody=A failed login attempt was dettected to your account on {0} from {1}. If this was not you, please contact an admin. +eventRemoveTotpSubject=Remove TOTP +eventRemoveTotpBody=TOTP was removed from your account on {0} from {1}. If this was not you, please contact an admin. +eventUpdatePasswordSubject=Update password +eventUpdatePasswordBody=Your password was changed on {0} from {1}. If this was not you, please contact an admin. +eventUpdateTotpSubject=Update TOTP +eventUpdateTotpBody=TOTP was updated for your account on {0} from {1}. If this was not you, please contact an admin. diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl index 5d277e5e2b..55f0138bef 100644 --- a/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl +++ b/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl @@ -1,5 +1 @@ -Someone just requested to change your Keycloak account's password. If this was you, click on the link below to set a new password: -${link} -This link will expire within ${linkExpiration} minutes. - -If you don't want to reset your password, just ignore this message and nothing will be changed. \ No newline at end of file +${formatter.format(rb.passwordResetBody,link, linkExpiration)} \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl index d71536c965..c884ad4b75 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl @@ -23,7 +23,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl index c881e9c9be..797b62d7ce 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl @@ -4,7 +4,7 @@ <#if section = "title"> ${rb.oauthGrantTitle} <#elseif section = "header"> - Temporary access for ${(realm.name)!''} requested by ${(client.clientId)!''}. + ${formatter.format(rb.oauthGrantTitleHtml,(realm.name!''), (client.clientId!''))} <#elseif section = "form">

${rb.oauthGrantRequest}

@@ -55,8 +55,8 @@
- - + +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl index aca7d76ac8..9fe7a15ac8 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl @@ -1,9 +1,9 @@ <#import "template.ftl" as layout> <@layout.registrationLayout displayInfo=true; section> <#if section = "title"> - ${rb.emailForgotHeader} + ${rb.emailForgotTitle} <#elseif section = "header"> - ${rb.emailForgotHeader} + ${rb.emailForgotTitle} <#elseif section = "form">
@@ -23,7 +23,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl index 613e8f2593..8661fa0fa5 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl @@ -1,9 +1,9 @@ <#import "template.ftl" as layout> <@layout.registrationLayout; section> <#if section = "title"> - ${rb.loginTitle} ${realm.name} + ${formatter.format(rb.loginTitle,realm.name)} <#elseif section = "header"> - ${rb.loginTitle} ${realm.name} + ${formatter.format(rb.loginTitleHtml,realm.name)} <#elseif section = "form">
@@ -27,8 +27,8 @@
- - + +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl index 826ef31416..bbc69a27f4 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl @@ -1,9 +1,9 @@ <#import "template.ftl" as layout> <@layout.registrationLayout displayInfo=true; section> <#if section = "title"> - ${rb.emailUpdateHeader} + ${rb.updatePasswordTitle} <#elseif section = "header"> - ${rb.emailUpdateHeader} + ${rb.updatePasswordTitle} <#elseif section = "form">
@@ -31,7 +31,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl index 812e29eb70..c3e1d9b303 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl @@ -40,7 +40,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl index 19e9bee15b..ac48f95c99 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl @@ -6,10 +6,10 @@ ${rb.emailVerifyTitle} <#elseif section = "form">

- ${rb.emailVerifyInstr} + ${rb.emailVerifyInstruction1}

-

${rb.emailVerifyInstrQ} - ${rb.emailVerifyClick} ${rb.emailVerifyResend} +

+ ${rb.emailVerifyInstruction2} ${rb.doClickHere} ${rb.emailVerifyInstruction3}

\ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl index e46cf9518d..e514b9ee6c 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl @@ -2,15 +2,15 @@ <@layout.registrationLayout displayInfo=social.displayInfo; section> <#if section = "title"> <#if client.application> - ${rb.loginTitle} ${realm.name} + ${formatter.format(rb.loginTitle,(realm.name!''))} <#elseif client.oauthClient> - ${realm.name} ${rb.loginOauthTitle} + ${formatter.format(rb.loginOauthTitle,(realm.name!''))} <#elseif section = "header"> <#if client.application> - ${rb.loginTitle} ${(realm.name)!''} + ${formatter.format(rb.loginTitleHtml,(realm.name!''))} <#elseif client.oauthClient> - Temporary access for ${(realm.name)!''} requested by ${(client.clientId)!''}. + ${formatter.format(rb.loginOauthTitleHtml,(realm.name!''), (client.clientId!''))} <#elseif section = "form"> <#if realm.password> @@ -41,24 +41,24 @@
<#if realm.resetPasswordAllowed> - ${rb.loginForgot} ${rb.password}? + ${rb.doForgotPassword}
- - + +
@@ -75,7 +75,7 @@ <#elseif section = "info" > <#if realm.password && realm.registrationAllowed>
- ${rb.noAccount} ${rb.register} + ${rb.noAccount} ${rb.doRegister}
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties deleted file mode 100755 index 0a59a69c69..0000000000 --- a/forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties +++ /dev/null @@ -1,126 +0,0 @@ -logIn=Log in -logInTo=Log in to -logInWith=Log in with -noAccount=New user? -register=Register -registerWith=Register with -allRequired=All fields are required -alreadyHaveAccount=Already have an account? -street=Street -locality=City or Locality -region=State, Province, or Region -postal_code=Zip or Postal code -country=Country - -poweredByKeycloak=Powered by Keycloak - -username=Username -usernameOrEmail=Username or email -fullName=Full name -firstName=First name -lastName=Last name -email=Email -password=Password -rememberMe=Remember me -passwordConfirm=Confirm password -passwordNew=New Password -passwordNewConfirm=New Password confirmation -passwordUpdated=Password updated -cancel=Cancel -accept=Accept -submit=Submit -yes=Yes -no=No - -authenticatorCode=One-time code -clientCertificate=Client Certificate - -invalidUser=Invalid username or password. -invalidPassword=Invalid username or password. -invalidEmail=Invalid email address -accountDisabled=Account is disabled, contact admin -accountTemporarilyDisabled=Account is temporarily disabled, contact admin or try again later -expiredCode=Login timeout. Please login again - -missingFirstName=Please specify first name -missingLastName=Please specify last name -missingEmail=Please specify email -missingUsername=Please specify username -missingPassword=Please specify password. -notMatchPassword=Passwords don't match -missingTotp=Please specify authenticator code - -invalidPasswordExisting=Invalid existing password -invalidPasswordConfirm=Password confirmation doesn't match -invalidTotp=Invalid authenticator code - -successTotp=Mobile authenticator configured. -successTotpRemoved=Mobile authenticator removed. - -usernameExists=Username already exists -emailExists=Email already exists - -federatedIdentityEmailExists=User with email already exists. Please login to account management to link the account. -federatedIdentityUsernameExists=User with username already exists. Please login to account management to link the account. -federatedIdentityRegistrationEmailMissing=Email is not provided. Use another provider to create account please. - -loginTitle=Log in to -loginOauthTitle=Temporary access. -loginOauthTitleHtml=Temporary access requested. Login to grant access. -loginForgot=Forgot - -loginTotpTitle=Mobile Authenticator Setup -loginTotpStep1=Install FreeOTP or Google Authenticator on your mobile -loginTotpStep2=Open the application and scan the barcode or enter the key -loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup -loginTotpOneTime=One-time code - -loginProfileTitle=Update Account Information -loginProfileWarning=Your account is not enabled because you need to update your account information. -loginProfileWarningFollow=Please follow the steps below. -loginProfileError=Some required fields are empty or incorrect. -loginProfileErrorSteps=Please correct the fields in red. - -oauthGrantTitle=OAuth Grant -oauthGrantTitleHtml=Temporary access requested -oauthGrantTerms=Keycloak Central Login and Google will use this information in accordance with their respective terms of service and privacy policies. -oauthGrantRequest=Do you grant these access privileges? -oauthGrantLoginRequest=Do you grant access? - -emailVerifyTitle=Email verification -emailVerifyInstr=An email with instructions to verify your email address has been sent to you. -emailVerifyInstrQ=Haven't received a verification code in your email? -emailVerifyClick=Click here -emailVerifyResend=to re-send the email. -emailVerified=Email verified - -error=A system error has occured, contact admin -errorTitle=We're sorry... -errorTitleHtml=We're sorry ... -errorGenericMsg=Something happened and we could not process your request. -actionWarningHeader=Your account is not enabled. -actionTotpWarning=You need to set up Mobile Authenticator to activate your account. -actionProfileWarning=You need to update your user profile to activate your account. -actionPasswordWarning=You need to change your password to activate your account. -actionEmailWarning=You need to verify your email address to activate your account. -actionFollow=Please fill in the fields below. - -errorKerberosLogin=Kerberos ticket not available. Authenticate with password. - -successHeader=Success! -errorHeader=Error! - -# Forgot password part - -emailForgotHeader=Forgot Your Password? -backToLogin=« Back to Login -backToApplication=« Back to Application -emailUpdateHeader=Update password -emailSent=You should receive an email shortly with further instructions. -emailSendError=Failed to send email, please try again later -emailError=Invalid email. -emailErrorInfo=Please, fill in the fields again. -emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password. - -accountUpdated=Your account has been updated -accountPasswordUpdated=Your password has been updated \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties new file mode 100644 index 0000000000..70d8aba28c --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties @@ -0,0 +1,148 @@ +doLogIn=Anmelden +doRegister=Registrieren +doCancel=Abbrechen +doSubmit=Absenden +doYes=Ja +doNo=Nein +doForgotPassword=Passwort vergessen? +doClickHere=hier klicken + +registerWithTitle=Registrierung bei {0} +registerWithTitleHtml=Registrierung bei {0} +loginTitle=Anmeldung bei {0} +loginTitleHtml=Anmeldung bei {0} +loginOauthTitle=Temporärer zugriff auf {0} +loginOauthTitleHtml=Temporärer zugriff auf {0} angefordert von {1}. +loginTotpTitle=Mobile Authentifizierung Einrichten +loginProfileTitle=Benutzer Konto Informatinen aktualisieren +oauthGrantTitle=OAuth gewähren +oauthGrantTitleHtml=Temporärer zugriff auf {0} angefordert von {1}. +errorTitle=Es tut uns leid... +errorTitleHtml=Es tut uns leid... +emailVerifyTitle=E-Mail verifizieren +emailForgotTitle=Passwort vergessen? +updatePasswordTitle=Passwort aktualisieren + +noAccount=Neuer Benutzer? +username=Benutzername +usernameOrEmail=Benutzername oder E-Mail +firstName=Vorname +fullName=Name +lastName=Nachname +email=E-Mail +password=Passwort +passwordConfirm=Passwort bestätigen +passwordNew=Neues Passwort +passwordNewConfirm=Neues Passwort bestätigen +rememberMe=Angemeldet bleiben +authenticatorCode=One-time Code +street=Strasse +region=Staat, Provinz, Region +postal_code=PLZ +locality=Stadt oder Ortschaft +country=Land + +loginTotpStep1=Installieren Sie FreeOTP oder Google Authenticator auf Ihrem Smartphone. +loginTotpStep2=Öffnen Sie die Applikation und scannen Sie den Barcode oder geben sie den Code ein. +loginTotpStep3=Geben Sie den One-time Code welcher die Applikation generiert hat ein und klicken Sie auf Absenden. +loginTotpOneTime=One-time Code + +oauthGrantRequest=Do you grant these access privileges? + +emailVerifyInstruction1=Ein E-Mail mit weitern Anweisungen wurde an Sie versendet. +emailVerifyInstruction2=Falls Sie kein E-Mail erhalten haben, dann können Sie +emailVerifyInstruction3=um ein neues E-Mail zu verschicken. + +backToLogin=« Zurück zur Anmeldung +backToApplication=« Zurück zur Applikation + +emailInstruction=Geben Sie ihren Benutzernamen oder E-Mail Adresse ein und klicken Sie auf Absenden. Danach werden wir ihnen ein E-Mail mit weiteren Instruktionen zusenden. + +invalidUserMessage=Ungültiger Benutzername oder Passwort. +invalidEmailMessage=Ungültige E-Mail Adresse. +accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin. +accountTemporarilyDisabledMessage=Benutzerkonto ist temporär gespert, bitte kontaktieren Sie den Admin oder versuchen Sie es später nocheinmal. +expiredCodeMessage=Zeitüberschreitung bei der Anmeldung. Bitter melden Sie sich erneut an. + +missingFirstNameMessage=Bitte geben Sie einen Vornamen ein. +missingLastNameMessage=Bitte geben Sie einen Nachnamen ein. +missingEmailMessage=Bitte geben Sie eine E-Mail Adresse ein. +missingUsernameMessage=Bitte geben Sie einen Benutzernamen ein. +missingPasswordMessage=Bitte geben Sie ein Passwort ein. +missingTotpMessage=Bitte geben Sie den One-time Code ein. +notMatchPasswordMessage=Passwörter sind nicht identisch. + +invalidPasswordExistingMessage=Das aktuelle Passwort is ungültig. +invalidPasswordConfirmMessage=Die Passwort bestätigung ist nicht identisch. +invalidTotpMessage=Ungültiger One-time Code. + +usernameExistsMessage=Benutzermane exisitert bereits. +emailExistsMessage=E-Mail existiert bereits. + +federatedIdentityRegistrationEmailMissingMessage=Die E-Mail Adresse ist nicht vorhanden. Bitte verwenden Sie einen anderen Provider um das Benutzerkonto zu erstellen. +federatedIdentityEmailExistsMessage=Es exisitert bereits ein Benutzer mit dieser E-Mail Adresse. Bitte melden Sie sich bei der Benutzerverwaltung an um das Benutzerkonto zu verknüpfen. +federatedIdentityUsernameExistsMessage=Es exisitert bereits ein Benutzer mit diesem Benutzernamen. Bitte melden Sie sich bei der Benutzerverwaltung an um das Benutzerkonto zu verknüpfen. + +configureTotpMessage=Sie müssen eine Mobile Authentifizierung einrichten um das Benutzerkonto zu aktivieren. +updateProfileMessage=Sie müssen ihr Benutzerkonto aktualisieren um das Benutzerkonto zu aktivieren. +updatePasswordMessage=Sie müssen ihr Passwort ändern um das Benutzerkonto zu aktivieren. +verifyEmailMessage=Sie müssen ihre E-Mail Adresse verifizieren um das Benutzerkonto zu aktivieren. + +emailSentMessage=Sie sollten in kürze ein E-Mail mit weiteren Instruktionen erhalten. +emailSendErrorMessage=Das E-Mail konnte nicht versendet werden, bitte versuchen Sie es später nochmals. + +accountUpdatedMessage=Ihr Benutzerkonto wurde aktualisiert. +accountPasswordUpdatedMessage=Ihr Passwort wurde aktualisiert. + +noAccessMessage=Kein Zugriff + +invalidPasswordMinLengthMessage=Ungültiges Passwort: minimum länge {0}. +invalidPasswordMinDigitsMessage=Ungültiges Passwort: muss mindestens {0} Zahl(en) beinhalten. +invalidPasswordMinLowerCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten. +invalidPasswordMinUpperCaseCharsMessage=Ungültiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten. +invalidPasswordMinSpecialCharsMessage=Ungültiges Passwort: muss mindestens {0} Spezialzeichen beinhalten. +invalidPasswordNotUsernameMessage=Ungültiges Passwort\: darf nicht gleich sein wie Benutzername. + +failedToProcessResponseMessage=Konnte Response nicht verarbeiten. +httpsRequiredMessage=HTTPS erforderlich. +realmNotEnabledMessage=Realm nicht aktiviert. +invalidRequestMessage=Ungültiger Request. +unknownLoginRequesterMessage=Ungültiger login requester +loginRequesterNotEnabledMessage=Login requester nicht aktiviert. +bearerOnlyMessage=Bearer-only Applikationen könne sich nicht via Browser anmelden. +directGrantsOnlyMessage=Direct-grants-only Clients könne sich nicht via Browser anmelden. +invalidRedirectUriMessage=Ungültige redirect uri. +unsupportedNameIdFormatMessage=Nicht unterstütztes NameIDFormat. +invlidRequesterMessage=Ungültiger requester. +registrationNotAllowedMessage=Registrierung nicht erlaubt. + +permissionNotApprovedMessage=Berechtigung nicht bestätigt. +noRelayStateInResponseMessage=Kein relay state in der Antwort von dem Identity Provider [{0}]. +identityProviderAlreadyLinkedMessage=Die Identität welche von dem Identity Provider [{0}] zurückgegeben wurde, ist bereits mit einem anderen Benutzer verknüpft. +insufficientPermissionMessage=Nicht genügtend Rechte um die Identität zu verknüpfen. +couldNotProceedWithAuthenticationRequestMessage=Konnte den Authentifizierungs Request nicht weiter verarbeiten. +couldNotObtainTokenMessage=Konnte kein token vom Identity Provider [{0}] entnehmen. +unexpectedErrorRetrievingTokenMessage=Unerwarteter Fehler während dem Empfang des Token von dem Identity Provider [{0}]. +unexpectedErrorHandlingResponseMessage=Unerwarteter Fehler während der bearbeitung des Respons vom Identity Provider [{0}]. +identityProviderAuthenticationFailedMessage=Authentifizierung Fehlgeschlagen. Konnte sich mit dem Identity Provider [{0}] nicht authentifizieren. +couldNotSendAuthenticationRequestMessage=Konnte Authentifizierungs Request nicht an den Identity Provider [{0}] schiken. +unexpectedErrorHandlingRequestMessage=Unerwarteter Fehler während der bearbeitung des Requests zum Identity Provider [{0}]. +invalidAccessCodeMessage=Ungültiger Access-Code. +sessionNotActiveMessage=Session nicht aktiv. +unknownCodeMessage=Unbekannter Code, bitte melden Sie sich erneut über die Applikation an. +invalidCodeMessage=Ungültiger Code, bitte melden Sie sich erneut über die Applikation an. +identityProviderUnexpectedErrorMessage=Unerwarteter Fehler während der Authentifizierung mit dem Identity Provider. +identityProviderNotFoundMessage=Konnte kein Identity Provider mit der Idenität [{0}] finden. +realmSupportsNoCredentialsMessage=Realm [{0}] unterstützt keine Credential Typen.. +identityProviderNotUniqueMessage=Realm [{0}] unterstütz mehrere Identity Providers. + +invalidParameterMessage=Invalid parameter\: {0} +missingParameterMessage=Missing parameter\: {0} +clientNotFoundMessage=Client not found. + +emailVerifiedMessage=Ihr E-Mail Addresse wurde erfolgreich verifiziert. + +locale_de=Deutsch +locale_en=Englisch + +poweredByKeycloak=Powered by Keycloak \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties new file mode 100755 index 0000000000..6f48c59dca --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties @@ -0,0 +1,146 @@ +doLogIn=Log in +doRegister=Register +doCancel=Cancel +doSubmit=Submit +doYes=Yes +doNo=No +doForgotPassword=Forgot Password? +doClickHere=Click here + +registerWithTitle=Register with {0} +registerWithTitleHtml=Register with {0} +loginTitle=Log in to {0} +loginTitleHtml=Log in to {0} +loginOauthTitle=Temporary access for {0} +loginOauthTitleHtml=Temporary access for {0} requested by {1}. +loginTotpTitle=Mobile Authenticator Setup +loginProfileTitle=Update Account Information +oauthGrantTitle=OAuth Grant +oauthGrantTitleHtml=Temporary access for {0} requested by {1}. +errorTitle=We're sorry... +errorTitleHtml=We're sorry ... +emailVerifyTitle=Email verification +emailForgotTitle=Forgot Your Password? +updatePasswordTitle=Update password + +noAccount=New user? +username=Username +usernameOrEmail=Username or email +firstName=First name +fullName=Full name +lastName=Last name +email=Email +password=Password +passwordConfirm=Confirm password +passwordNew=New Password +passwordNewConfirm=New Password confirmation +rememberMe=Remember me +authenticatorCode=One-time code +street=Street +locality=City or Locality +region=State, Province, or Region +postal_code=Zip or Postal code +country=Country + +loginTotpStep1=Install FreeOTP or Google Authenticator on your mobile +loginTotpStep2=Open the application and scan the barcode or enter the key +loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup +loginTotpOneTime=One-time code + +oauthGrantRequest=Do you grant these access privileges? + +emailVerifyInstruction1=An email with instructions to verify your email address has been sent to you. +emailVerifyInstruction2=Haven't received a verification code in your email? +emailVerifyInstruction3=to re-send the email. + +backToLogin=« Back to Login + +emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password. + +invalidUserMessage=Invalid username or password. +invalidEmailMessage=Invalid email address. +accountDisabledMessage=Account is disabled, contact admin. +accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later. +expiredCodeMessage=Login timeout. Please login again. + +missingFirstNameMessage=Please specify first name. +missingLastNameMessage=Please specify last name. +missingEmailMessage=Please specify email. +missingUsernameMessage=Please specify username. +missingPasswordMessage=Please specify password. +missingTotpMessage=Please specify authenticator code. +notMatchPasswordMessage=Passwords don't match. + +invalidPasswordExistingMessage=Invalid existing password. +invalidPasswordConfirmMessage=Password confirmation doesn't match. +invalidTotpMessage=Invalid authenticator code. + +usernameExistsMessage=Username already exists. +emailExistsMessage=Email already exists. + +federatedIdentityEmailExistsMessage=User with email already exists. Please login to account management to link the account. +federatedIdentityUsernameExistsMessage=User with username already exists. Please login to account management to link the account. + +configureTotpMessage=You need to set up Mobile Authenticator to activate your account. +updateProfileMessage=You need to update your user profile to activate your account. +updatePasswordMessage=You need to change your password to activate your account. +verifyEmailMessage=You need to verify your email address to activate your account. + +emailSentMessage=You should receive an email shortly with further instructions. +emailSendErrorMessage=Failed to send email, please try again later + +accountUpdatedMessage=Your account has been updated +accountPasswordUpdatedMessage=Your password has been updated + +noAccessMessage=No access + +invalidPasswordMinLengthMessage=Invalid password: minimum length {0} +invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits +invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters +invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters +invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters +invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username + +failedToProcessResponseMessage=Failed to process response +httpsRequiredMessage=HTTPS required +realmNotEnabledMessage=Realm not enabled +invalidRequestMessage=Invalid Request +unknownLoginRequesterMessage=Unknown login requester +loginRequesterNotEnabledMessage=Login requester not enabled +bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login +directGrantsOnlyMessage=Direct-grants-only clients are not allowed to initiate browser login +invalidRedirectUriMessage=Invalid redirect uri +unsupportedNameIdFormatMessage=Unsupported NameIDFormat +invlidRequesterMessage=Invalid requester +registrationNotAllowedMessage=Registration not allowed + +permissionNotApprovedMessage=Permission not approved. +noRelayStateInResponseMessage=No relay state in response from identity provider [{0}]. +identityProviderAlreadyLinkedMessage=The identity returned by the identity provider [{0}] is already linked to another user. +insufficientPermissionMessage=Insufficient permissions to link identities. +couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider. +couldNotObtainTokenMessage=Could not obtain token from identity provider [{0}]. +unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider [{0}]. +unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider [{0}]. +identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider [{0}]. +couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider [{0}]. +unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider [{0}]. +invalidAccessCodeMessage=Invalid access code. +sessionNotActiveMessage=Session not active. +unknownCodeMessage=Unknown code, please login again through your application. +invalidCodeMessage=Invalid code, please login again through your application. +identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider +identityProviderNotFoundMessage=Could not find an identity provider with the identifier [{0}]. +realmSupportsNoCredentialsMessage=Realm [{0}] does not support any credential type. +identityProviderNotUniqueMessage=Realm [{0}] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with. +emailVerifiedMessage=Your email address has been verified. + +locale_de=German +locale_en=English + +poweredByKeycloak=Powered by Keycloak +backToApplication=« Back to Application +missingParameterMessage=Missing parameters\: {0} +clientNotFoundMessage=Client not found. +invalidParameterMessage=Invalid parameter\: {0} +federatedIdentityRegistrationEmailMissingMessage=Email is not provided. Use another provider to create account please. diff --git a/forms/common-themes/src/main/resources/theme/login/base/register.ftl b/forms/common-themes/src/main/resources/theme/login/base/register.ftl index 492fbabd52..8dcdcea481 100755 --- a/forms/common-themes/src/main/resources/theme/login/base/register.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/register.ftl @@ -1,9 +1,9 @@ <#import "template.ftl" as layout> <@layout.registrationLayout; section> <#if section = "title"> - ${rb.registerWith} ${realm.name} + ${formatter.format(rb.registerWithTitle,(realm.name!''))} <#elseif section = "header"> - ${rb.registerWith} ${realm.name} + ${formatter.format(rb.registerWithTitleHtml,(realm.name!''))} <#elseif section = "form">
<#if !realm.registrationEmailAsUsername> @@ -116,7 +116,7 @@
- +
diff --git a/forms/common-themes/src/main/resources/theme/login/base/template.ftl b/forms/common-themes/src/main/resources/theme/login/base/template.ftl index 49c02469b2..c09271da62 100644 --- a/forms/common-themes/src/main/resources/theme/login/base/template.ftl +++ b/forms/common-themes/src/main/resources/theme/login/base/template.ftl @@ -21,6 +21,19 @@ + <#if realm.internationalizationEnabled> + + @@ -31,6 +44,15 @@
<#nested "header">
+ <#if realm.internationalizationEnabled> +
+ +
+
<#if displayMessage && message?has_content> diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java index df43d16fb2..21972b2c00 100755 --- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java +++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java @@ -5,9 +5,12 @@ import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; import org.keycloak.email.freemarker.beans.EventBean; import org.keycloak.events.Event; +import org.keycloak.events.EventType; import org.keycloak.freemarker.FreeMarkerUtil; +import org.keycloak.freemarker.LocaleHelper; import org.keycloak.freemarker.Theme; import org.keycloak.freemarker.ThemeProvider; +import org.keycloak.freemarker.beans.TextFormatterBean; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; @@ -17,10 +20,7 @@ import javax.mail.Session; import javax.mail.Transport; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; +import java.util.*; /** * @author Stian Thorgersen @@ -56,7 +56,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { Map attributes = new HashMap(); attributes.put("event", new EventBean(event)); - send("passwordResetSubject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes); + send(toCamelCase(event.getType()) + "Subject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes); } @Override @@ -81,8 +81,12 @@ public class FreeMarkerEmailProvider implements EmailProvider { try { ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL); - - String subject = theme.getMessages().getProperty(subjectKey); + Locale locale = LocaleHelper.getLocale(realm, user); + attributes.put("locale", locale); + Properties rb = theme.getMessages(locale); + attributes.put("rb", rb); + attributes.put("formatter", new TextFormatterBean(locale)); + String subject = rb.getProperty(subjectKey); String body = freeMarker.processTemplate(attributes, template, theme); send(subject, body); @@ -150,4 +154,12 @@ public class FreeMarkerEmailProvider implements EmailProvider { public void close() { } + private String toCamelCase(EventType event){ + StringBuilder sb = new StringBuilder("event"); + for(String s : event.name().toString().toLowerCase().split("_")){ + sb.append(s.substring(0,1).toUpperCase()).append(s.substring(1)); + } + return sb.toString(); + } + } diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java index 11c26fe767..36a5252515 100755 --- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java +++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java @@ -7,6 +7,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.provider.Provider; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -22,6 +23,8 @@ public interface LoginFormsProvider extends Provider { public LoginFormsProvider setUriInfo(UriInfo uriInfo); + public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders); + public Response createResponse(UserModel.RequiredAction action); public Response createLogin(); @@ -45,11 +48,11 @@ public interface LoginFormsProvider extends Provider { public LoginFormsProvider setAccessRequest(List realmRolesRequested, MultivaluedMap resourceRolesRequested); public LoginFormsProvider setAccessRequest(String message); - public LoginFormsProvider setError(String message); + public LoginFormsProvider setError(String message, Object ... parameters); - public LoginFormsProvider setSuccess(String message); + public LoginFormsProvider setSuccess(String message, Object ... parameters); - public LoginFormsProvider setWarning(String message); + public LoginFormsProvider setWarning(String message, Object ... parameters); public LoginFormsProvider setUser(UserModel user); diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java index be59af04a3..14579b9378 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java @@ -5,11 +5,8 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.keycloak.OAuth2Constants; import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; -import org.keycloak.freemarker.BrowserSecurityHeaderSetup; -import org.keycloak.freemarker.FreeMarkerException; -import org.keycloak.freemarker.FreeMarkerUtil; -import org.keycloak.freemarker.Theme; -import org.keycloak.freemarker.ThemeProvider; +import org.keycloak.freemarker.*; +import org.keycloak.freemarker.beans.TextFormatterBean; import org.keycloak.login.LoginFormsPages; import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.freemarker.model.ClientBean; @@ -33,23 +30,17 @@ import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.flows.Urls; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.core.*; import java.io.IOException; import java.net.URI; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.text.MessageFormat; +import java.util.*; import java.util.concurrent.TimeUnit; /** * @author Stian Thorgersen */ -public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { + public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class); @@ -63,6 +54,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { private Map httpResponseHeaders = new HashMap(); private String accessRequestMessage; private URI actionUri; + private Object[] parameters; private String message; private MessageType messageType = MessageType.ERROR; @@ -80,6 +72,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { private UriInfo uriInfo; + private HttpHeaders httpHeaders; + public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) { this.session = session; this.freeMarker = freeMarker; @@ -95,21 +89,27 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { return this; } + @Override + public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders) { + this.httpHeaders = httpHeaders; + return this; + } + public Response createResponse(UserModel.RequiredAction action) { String actionMessage; LoginFormsPages page; switch (action) { case CONFIGURE_TOTP: - actionMessage = Messages.ACTION_WARN_TOTP; + actionMessage = Messages.CONFIGURE_TOTP; page = LoginFormsPages.LOGIN_CONFIG_TOTP; break; case UPDATE_PROFILE: - actionMessage = Messages.ACTION_WARN_PROFILE; + actionMessage = Messages.UPDATE_PROFILE; page = LoginFormsPages.LOGIN_UPDATE_PROFILE; break; case UPDATE_PASSWORD: - actionMessage = Messages.ACTION_WARN_PASSWD; + actionMessage = Messages.UPDATE_PASSWORD; page = LoginFormsPages.LOGIN_UPDATE_PASSWORD; break; case VERIFY_EMAIL: @@ -123,10 +123,10 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendVerifyEmail(link, expiration); } catch (EmailException e) { logger.error("Failed to send verification email", e); - return setError("emailSendError").createErrorPage(); + return setError(Messages.EMAIL_SENT_ERROR).createErrorPage(); } - actionMessage = Messages.ACTION_WARN_EMAIL; + actionMessage = Messages.VERIFY_EMAIL; page = LoginFormsPages.LOGIN_VERIFY_EMAIL; break; default: @@ -175,8 +175,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { } Properties messages; + Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, httpHeaders); + if(locale != null){ + attributes.put("locale", locale); + attributes.put("formatter", new TextFormatterBean(locale)); + } try { - messages = theme.getMessages(); + messages = theme.getMessages(locale); attributes.put("rb", messages); } catch (IOException e) { logger.warn("Failed to load messages", e); @@ -184,7 +189,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { } if (message != null) { - attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType)); + String formattedMessage; + if(messages.containsKey(message)){ + formattedMessage = new MessageFormat(messages.getProperty(message).replace("'","''"),locale).format(parameters); + }else{ + formattedMessage = message; + } + attributes.put("message", new MessageBean(formattedMessage, messageType)); } if (page == LoginFormsPages.OAUTH_GRANT) { // for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param @@ -233,6 +244,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { for (Map.Entry entry : httpResponseHeaders.entrySet()) { builder.header(entry.getKey(), entry.getValue()); } + LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri, realm.getName())); return builder.build(); } catch (FreeMarkerException e) { logger.error("Failed to process template", e); @@ -277,21 +289,24 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { return createResponse(LoginFormsPages.CODE); } - public FreeMarkerLoginFormsProvider setError(String message) { + public FreeMarkerLoginFormsProvider setError(String message, Object ... parameters) { this.message = message; this.messageType = MessageType.ERROR; + this.parameters = parameters; return this; } - public FreeMarkerLoginFormsProvider setSuccess(String message) { + public FreeMarkerLoginFormsProvider setSuccess(String message, Object ... parameters) { this.message = message; this.messageType = MessageType.SUCCESS; + this.parameters = parameters; return this; } - public FreeMarkerLoginFormsProvider setWarning(String message) { + public FreeMarkerLoginFormsProvider setWarning(String message, Object ... parameters) { this.message = message; this.messageType = MessageType.WARNING; + this.parameters = parameters; return this; } diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java index e4ac27f406..3e36d08c9f 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java @@ -25,6 +25,8 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.representations.idm.CredentialRepresentation; +import java.util.Set; + /** * @author Stian Thorgersen */ @@ -60,6 +62,14 @@ public class RealmBean { return realm.isRememberMe(); } + public boolean isInternationalizationEnabled() { + return realm.isInternationalizationEnabled(); + } + + public Set getSupportedLocales(){ + return realm.getSupportedLocales(); + } + public boolean isPassword() { for (RequiredCredentialModel r : realm.getRequiredCredentials()) { if (r.getType().equals(CredentialRepresentation.PASSWORD)) { diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java index 26d67a3bc8..259b09856c 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java @@ -86,6 +86,10 @@ public class UrlBean { return Urls.loginActionEmailVerification(baseURI, realm).toString(); } + public String getLocaleCookiePath(){ + return Urls.localeCookiePath(baseURI, realm); + } + public String getOauthAction() { if (this.actionuri != null) { return this.actionuri.getPath(); diff --git a/model/api/src/main/java/org/keycloak/models/ModelException.java b/model/api/src/main/java/org/keycloak/models/ModelException.java index 1c2f068dad..d3431b056b 100644 --- a/model/api/src/main/java/org/keycloak/models/ModelException.java +++ b/model/api/src/main/java/org/keycloak/models/ModelException.java @@ -5,6 +5,8 @@ package org.keycloak.models; */ public class ModelException extends RuntimeException { + private Object[] parameters; + public ModelException() { } @@ -12,6 +14,11 @@ public class ModelException extends RuntimeException { super(message); } + public ModelException(String message, Object ... parameters) { + super(message); + this.parameters = parameters; + } + public ModelException(String message, Throwable cause) { super(message, cause); } @@ -20,4 +27,11 @@ public class ModelException extends RuntimeException { super(cause); } + public Object[] getParameters() { + return parameters; + } + + public void setParameters(Object[] parameters) { + this.parameters = parameters; + } } diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java index 3651e9d3f4..cfcc108db8 100755 --- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java +++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java @@ -1,5 +1,6 @@ package org.keycloak.models; +import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -9,6 +10,13 @@ import java.util.List; */ public class PasswordPolicy { + public static final String INVALID_PASSWORD_MIN_LENGTH_MESSAGE = "invalidPasswordMinLengthMessage"; + public static final String INVALID_PASSWORD_MIN_DIGITS_MESSAGE = "invalidPasswordMinDigitsMessage"; + public static final String INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE = "invalidPasswordMinLowerCaseCharsMessage"; + public static final String INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE = "invalidPasswordMinUpperCaseCharsMessage"; + public static final String INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE = "invalidPasswordMinSpecialCharsMessage"; + public static final String INVALID_PASSWORD_NOT_USERNAME = "invalidPasswordNotUsernameMessage"; + private List policies; private String policyString; @@ -76,9 +84,9 @@ public class PasswordPolicy { return -1; } - public String validate(String username, String password) { + public Error validate(String username, String password) { for (Policy p : policies) { - String error = p.validate(username, password); + Error error = p.validate(username, password); if (error != null) { return error; } @@ -87,7 +95,25 @@ public class PasswordPolicy { } private static interface Policy { - public String validate(String username, String password); + public Error validate(String username, String password); + } + + public static class Error{ + private String message; + private Object[] parameters; + + private Error(String message, Object ... parameters){ + this.message = message; + this.parameters = parameters; + } + + public String getMessage() { + return message; + } + + public Object[] getParameters() { + return parameters; + } } private static class HashIterations implements Policy { @@ -99,7 +125,7 @@ public class PasswordPolicy { } @Override - public String validate(String username, String password) { + public Error validate(String username, String password) { return null; } } @@ -111,8 +137,8 @@ public class PasswordPolicy { } @Override - public String validate(String username, String password) { - return username.equals(password) ? "Invalid password: must not be equal to the username" : null; + public Error validate(String username, String password) { + return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null; } } @@ -125,8 +151,8 @@ public class PasswordPolicy { } @Override - public String validate(String username, String password) { - return password.length() < min ? "Invalid password: minimum length " + min : null; + public Error validate(String username, String password) { + return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null; } } @@ -139,14 +165,14 @@ public class PasswordPolicy { } @Override - public String validate(String username, String password) { + public Error validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isDigit(c)) { count++; } } - return count < min ? "Invalid password: must contain at least " + min + " numerical digits" : null; + return count < min ? new Error(INVALID_PASSWORD_MIN_DIGITS_MESSAGE, min) : null; } } @@ -159,14 +185,14 @@ public class PasswordPolicy { } @Override - public String validate(String username, String password) { + public Error validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isLowerCase(c)) { count++; } } - return count < min ? "Invalid password: must contain at least " + min + " lower case characters": null; + return count < min ? new Error(INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE, min): null; } } @@ -179,14 +205,14 @@ public class PasswordPolicy { } @Override - public String validate(String username, String password) { + public Error validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (Character.isUpperCase(c)) { count++; } } - return count < min ? "Invalid password: must contain at least " + min + " upper case characters" : null; + return count < min ? new Error(INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE, min) : null; } } @@ -199,14 +225,14 @@ public class PasswordPolicy { } @Override - public String validate(String username, String password) { + public Error validate(String username, String password) { int count = 0; for (char c : password.toCharArray()) { if (!Character.isLetterOrDigit(c)) { count++; } } - return count < min ? "Invalid password: must contain at least " + min + " special characters" : null; + return count < min ? new Error(INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE, min) : null; } } diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java index ab260a13a3..7308565573 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -244,4 +244,11 @@ public interface RealmModel extends RoleContainerModel { ClientModel findClientById(String id); boolean isIdentityFederationEnabled(); + + boolean isInternationalizationEnabled(); + void setInternationalizationEnabled(boolean enabled); + Set getSupportedLocales(); + void setSupportedLocales(Set locales); + String getDefaultLocale(); + void setDefaultLocale(String locale); } diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java index a360f958a4..1016341e3a 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java @@ -323,8 +323,8 @@ public class UserFederationManager implements UserProvider { public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) { if (credential.getType().equals(UserCredentialModel.PASSWORD)) { if (realm.getPasswordPolicy() != null) { - String error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue()); - if (error != null) throw new ModelException(error); + PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue()); + if (error != null) throw new ModelException(error.getMessage(), error.getParameters()); } } user.updateCredential(credential); diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java index 1047ad252b..ee8ce80d0c 100755 --- a/model/api/src/main/java/org/keycloak/models/UserModel.java +++ b/model/api/src/main/java/org/keycloak/models/UserModel.java @@ -13,6 +13,7 @@ public interface UserModel { public static final String LAST_NAME = "lastName"; public static final String FIRST_NAME = "firstName"; public static final String EMAIL = "email"; + public static final String LOCALE = "locale"; String getId(); diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java index 38baa89319..678fb3326a 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -65,6 +65,10 @@ public class RealmEntity extends AbstractIdentifiableEntity { private String adminAppId; + private boolean internationalizationEnabled; + private List supportedLocales = new ArrayList(); + private String defaultLocale; + public String getName() { return name; } @@ -407,6 +411,30 @@ public class RealmEntity extends AbstractIdentifiableEntity { public void setCertificatePem(String certificatePem) { this.certificatePem = certificatePem; } + + public boolean isInternationalizationEnabled() { + return internationalizationEnabled; + } + + public void setInternationalizationEnabled(boolean internationalizationEnabled) { + this.internationalizationEnabled = internationalizationEnabled; + } + + public List getSupportedLocales() { + return supportedLocales; + } + + public void setSupportedLocales(List supportedLocales) { + this.supportedLocales = supportedLocales; + } + + public String getDefaultLocale() { + return defaultLocale; + } + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 686eede868..736aff8b12 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -155,6 +155,10 @@ public class ModelToRepresentation { rep.addIdentityProvider(toRepresentation(provider)); } + rep.setInternationalizationEnabled(realm.isInternationalizationEnabled()); + rep.getSupportedLocales().addAll(realm.getSupportedLocales()); + rep.setDefaultLocale(realm.getDefaultLocale()); + return rep; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index ce87adae13..c2090d3ce9 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -243,6 +243,16 @@ public class RepresentationToModel { UserModel user = createUser(session, newRealm, userRep, appMap); } } + + if(rep.isInternationalizationEnabled() != null){ + newRealm.setInternationalizationEnabled(rep.isInternationalizationEnabled()); + } + if(rep.getSupportedLocales() != null){ + newRealm.setSupportedLocales(new HashSet(rep.getSupportedLocales())); + } + if(rep.getDefaultLocale() != null){ + newRealm.setDefaultLocale(rep.getDefaultLocale()); + } } public static void updateRealm(RealmRepresentation rep, RealmModel realm) { @@ -304,6 +314,16 @@ public class RepresentationToModel { if ("GENERATE".equals(rep.getPublicKey())) { KeycloakModelUtils.generateRealmKeys(realm); } + + if(rep.isInternationalizationEnabled() != null){ + realm.setInternationalizationEnabled(rep.isInternationalizationEnabled()); + } + if(rep.getSupportedLocales() != null){ + realm.setSupportedLocales(new HashSet(rep.getSupportedLocales())); + } + if(rep.getDefaultLocale() != null){ + realm.setDefaultLocale(rep.getDefaultLocale()); + } } // Basic realm stuff diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java index 7242c554df..3bde361113 100644 --- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java +++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java @@ -11,62 +11,72 @@ public class PasswordPolicyTest { @Test public void testLength() { PasswordPolicy policy = new PasswordPolicy("length"); - Assert.assertEquals("Invalid password: minimum length 8", policy.validate("jdoe", "1234567")); + Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "1234567").getMessage()); + Assert.assertArrayEquals(new Object[]{8}, policy.validate("jdoe", "1234567").getParameters()); Assert.assertNull(policy.validate("jdoe", "12345678")); policy = new PasswordPolicy("length(4)"); - Assert.assertEquals("Invalid password: minimum length 4", policy.validate("jdoe", "123")); + Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "123").getMessage()); + Assert.assertArrayEquals(new Object[]{4}, policy.validate("jdoe", "123").getParameters()); Assert.assertNull(policy.validate("jdoe", "1234")); } @Test public void testDigits() { PasswordPolicy policy = new PasswordPolicy("digits"); - Assert.assertEquals("Invalid password: must contain at least 1 numerical digits", policy.validate("jdoe", "abcd")); + Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd").getMessage()); + Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd").getParameters()); Assert.assertNull(policy.validate("jdoe", "abcd1")); policy = new PasswordPolicy("digits(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 numerical digits", policy.validate("jdoe", "abcd1")); + Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd1").getMessage()); + Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abcd1").getParameters()); Assert.assertNull(policy.validate("jdoe", "abcd12")); } @Test public void testLowerCase() { PasswordPolicy policy = new PasswordPolicy("lowerCase"); - Assert.assertEquals("Invalid password: must contain at least 1 lower case characters", policy.validate("jdoe", "ABCD1234")); + Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABCD1234").getMessage()); + Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "ABCD1234").getParameters()); Assert.assertNull(policy.validate("jdoe", "ABcD1234")); policy = new PasswordPolicy("lowerCase(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 lower case characters", policy.validate("jdoe", "ABcD1234")); + Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABcD1234").getMessage()); + Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ABcD1234").getParameters()); Assert.assertNull(policy.validate("jdoe", "aBcD1234")); } @Test public void testUpperCase() { PasswordPolicy policy = new PasswordPolicy("upperCase"); - Assert.assertEquals("Invalid password: must contain at least 1 upper case characters", policy.validate("jdoe", "abcd1234")); + Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abcd1234").getMessage()); + Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters()); Assert.assertNull(policy.validate("jdoe", "abCd1234")); policy = new PasswordPolicy("upperCase(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 upper case characters", policy.validate("jdoe", "abCd1234")); + Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abCd1234").getMessage()); + Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abCd1234").getParameters()); Assert.assertNull(policy.validate("jdoe", "AbCd1234")); } @Test public void testSpecialChars() { PasswordPolicy policy = new PasswordPolicy("specialChars"); - Assert.assertEquals("Invalid password: must contain at least 1 special characters", policy.validate("jdoe", "abcd1234")); + Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "abcd1234").getMessage()); + Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters()); Assert.assertNull(policy.validate("jdoe", "ab&d1234")); policy = new PasswordPolicy("specialChars(2)"); - Assert.assertEquals("Invalid password: must contain at least 2 special characters", policy.validate("jdoe", "ab&d1234")); + Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "ab&d1234").getMessage()); + Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ab&d1234").getParameters()); Assert.assertNull(policy.validate("jdoe", "ab&d-234")); } @Test public void testNotUsername() { PasswordPolicy policy = new PasswordPolicy("notUsername"); - Assert.assertEquals("Invalid password: must not be equal to the username", policy.validate("jdoe", "jdoe")); + Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage()); Assert.assertNull(policy.validate("jdoe", "ab&d1234")); } diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java index 5183b74735..99ab488fe4 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java @@ -1067,6 +1067,36 @@ public class RealmAdapter implements RealmModel { realm.setAccessCodeLifespanLogin(accessCodeLifespanLogin); } + @Override + public boolean isInternationalizationEnabled() { + return realm.isInternationalizationEnabled(); + } + + @Override + public void setInternationalizationEnabled(boolean enabled) { + realm.setInternationalizationEnabled(enabled); + } + + @Override + public Set getSupportedLocales() { + return new HashSet<>(realm.getSupportedLocales()); + } + + @Override + public void setSupportedLocales(Set locales) { + realm.setSupportedLocales(new ArrayList<>(locales)); + } + + @Override + public String getDefaultLocale() { + return realm.getDefaultLocale(); + } + + @Override + public void setDefaultLocale(String locale) { + realm.setDefaultLocale(locale); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java index a58774d369..77b84e64aa 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java @@ -888,4 +888,39 @@ public class RealmAdapter implements RealmModel { public int hashCode() { return getId().hashCode(); } + + @Override + public boolean isInternationalizationEnabled() { + if (updated != null) return updated.isInternationalizationEnabled(); + return cached.isInternationalizationEnabled(); + } + + @Override + public void setInternationalizationEnabled(boolean enabled) { + getDelegateForUpdate(); + updated.setInternationalizationEnabled(enabled); + } + + @Override + public Set getSupportedLocales() { + if (updated != null) return updated.getSupportedLocales(); + return cached.getSupportedLocales(); + } + + @Override + public void setSupportedLocales(Set locales) { + getDelegateForUpdate(); + updated.setSupportedLocales(locales); + } + + @Override + public String getDefaultLocale() { + if (updated != null) return updated.getDefaultLocale(); + return cached.getDefaultLocale(); + } + + @Override + public void setDefaultLocale(String locale) { + updated.setDefaultLocale(locale); + } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java index dc062401c6..a4ce5bb653 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java @@ -83,6 +83,9 @@ public class CachedRealm { private Map realmRoles = new HashMap(); private Map applications = new HashMap(); private Map clients = new HashMap(); + private boolean internationalizationEnabled; + private Set supportedLocales = new HashSet(); + private String defaultLocale; public CachedRealm() { } @@ -164,6 +167,10 @@ public class CachedRealm { cache.addCachedOAuthClient(cachedApp); } + internationalizationEnabled = model.isInternationalizationEnabled(); + supportedLocales.addAll(model.getSupportedLocales()); + defaultLocale = model.getDefaultLocale(); + } @@ -353,4 +360,16 @@ public class CachedRealm { public List getIdentityProviders() { return identityProviders; } + + public boolean isInternationalizationEnabled() { + return internationalizationEnabled; + } + + public Set getSupportedLocales() { + return supportedLocales; + } + + public String getDefaultLocale() { + return defaultLocale; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index a17dd1791d..73add51137 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1238,4 +1238,36 @@ public class RealmAdapter implements RealmModel { return !this.realm.getIdentityProviders().isEmpty(); } + @Override + public boolean isInternationalizationEnabled() { + return realm.isInternationalizationEnabled(); + } + + @Override + public void setInternationalizationEnabled(boolean enabled) { + realm.setInternationalizationEnabled(enabled); + em.flush(); + } + + @Override + public Set getSupportedLocales() { + return realm.getSupportedLocales(); + } + + @Override + public void setSupportedLocales(Set locales) { + realm.setSupportedLocales(locales); + em.flush(); + } + + @Override + public String getDefaultLocale() { + return realm.getDefaultLocale(); + } + + @Override + public void setDefaultLocale(String locale) { + realm.setDefaultLocale(locale); + em.flush(); + } } \ No newline at end of file diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index 1ba7a5573c..563791eaa0 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java @@ -137,6 +137,18 @@ public class RealmEntity { @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") protected List identityProviders = new ArrayList(); + @Column(name="INTERNATIONALIZATION_ENABLED") + protected boolean internationalizationEnabled; + + @ElementCollection + @Column(name="VALUE") + @CollectionTable(name="REALM_SUPPORTED_LOCALES", joinColumns={ @JoinColumn(name="REALM_ID") }) + protected Set supportedLocales = new HashSet(); + + @Column(name="DEFAULT_LOCALE") + protected String defaultLocale; + + public String getId() { return id; } @@ -452,5 +464,28 @@ public class RealmEntity { getIdentityProviders().add(entity); } + public boolean isInternationalizationEnabled() { + return internationalizationEnabled; + } + + public void setInternationalizationEnabled(boolean internationalizationEnabled) { + this.internationalizationEnabled = internationalizationEnabled; + } + + public Set getSupportedLocales() { + return supportedLocales; + } + + public void setSupportedLocales(Set supportedLocales) { + this.supportedLocales = supportedLocales; + } + + public String getDefaultLocale() { + return defaultLocale; + } + + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 9bee5a52c0..655a45293c 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1085,5 +1085,40 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return getId().hashCode(); } + @Override + public boolean isInternationalizationEnabled() { + return realm.isInternationalizationEnabled(); + } + @Override + public void setInternationalizationEnabled(boolean enabled) { + realm.setInternationalizationEnabled(enabled); + updateRealm(); + } + + @Override + public Set getSupportedLocales() { + return new HashSet(realm.getSupportedLocales()); + } + + @Override + public void setSupportedLocales(Set locales) { + if (locales != null) { + realm.setEventsListeners(new ArrayList(locales)); + } else { + realm.setEventsListeners(Collections.EMPTY_LIST); + } + updateRealm(); + } + + @Override + public String getDefaultLocale() { + return realm.getDefaultLocale(); + } + + @Override + public void setDefaultLocale(String locale) { + realm.setDefaultLocale(locale); + updateRealm(); + } } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index 183ebd571c..0e4b089385 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -22,6 +22,7 @@ import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper; import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ResourceAdminManager; +import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.admin.ClientAttributeCertificateResource; import org.keycloak.services.resources.flows.Flows; @@ -94,6 +95,7 @@ public class SamlProtocol implements LoginProtocol { protected UriInfo uriInfo; + protected HttpHeaders headers; @Override @@ -114,6 +116,12 @@ public class SamlProtocol implements LoginProtocol { return this; } + @Override + public SamlProtocol setHttpHeaders(HttpHeaders headers){ + this.headers = headers; + return this; + } + @Override public Response cancelLogin(ClientSessionModel clientSession) { return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get()); @@ -141,7 +149,7 @@ public class SamlProtocol implements LoginProtocol { return builder.redirectBinding().response(); } } catch (Exception e) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE ); } } @@ -295,7 +303,7 @@ public class SamlProtocol implements LoginProtocol { samlDocument = builder.buildDocument(samlModel); } catch (Exception e) { logger.error("failed", e); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.FAILED_TO_PROCESS_RESPONSE); } SAML2BindingBuilder2 bindingBuilder = new SAML2BindingBuilder2(); @@ -317,7 +325,7 @@ public class SamlProtocol implements LoginProtocol { publicKey = SamlProtocolUtils.getEncryptionValidationKey(client); } catch (Exception e) { logger.error("failed", e); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE); } bindingBuilder.encrypt(publicKey); } @@ -329,7 +337,7 @@ public class SamlProtocol implements LoginProtocol { } } catch (Exception e) { logger.error("failed", e); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE ); } } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java index 80420318ba..3e7cd2f300 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -23,6 +23,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.HttpAuthenticationManager; +import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.RealmsResource; import org.keycloak.services.resources.flows.Flows; import org.keycloak.util.StreamUtil; @@ -106,18 +107,18 @@ public class SamlService { if (!checkSsl()) { event.event(EventType.LOGIN); event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED ); } if (!realm.isEnabled()) { event.event(EventType.LOGIN_ERROR); event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); } if (samlRequest == null && samlResponse == null) { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST ); } return null; @@ -131,7 +132,7 @@ public class SamlService { if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) { event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE); event.detail(Details.REASON, "invalid_destination"); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST); } AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false); @@ -139,7 +140,7 @@ public class SamlService { logger.warn("Unknown saml response."); event.event(EventType.LOGOUT); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST); } // assume this is a logout response UserSessionModel userSession = authResult.getSession(); @@ -148,10 +149,10 @@ public class SamlService { logger.warn("UserSession is not tagged as logging out."); event.event(EventType.LOGOUT); event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST); } logger.debug("logout response"); - Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection); + Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers); event.success(); return response; } @@ -161,7 +162,7 @@ public class SamlService { if (documentHolder == null) { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST); } SAML2Object samlObject = documentHolder.getSamlObject(); @@ -173,23 +174,23 @@ public class SamlService { if (client == null) { event.event(EventType.LOGIN); event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER); } if (!client.isEnabled()) { event.event(EventType.LOGIN); event.error(Errors.CLIENT_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED); } if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) { event.event(EventType.LOGIN); event.error(Errors.NOT_ALLOWED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.BEARER_ONLY); } if (client.isDirectGrantsOnly()) { event.event(EventType.LOGIN); event.error(Errors.NOT_ALLOWED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.DIRECT_GRANTS_ONLY ); } try { @@ -198,7 +199,7 @@ public class SamlService { SamlService.logger.error("request validation failed", e); event.event(EventType.LOGIN); event.error(Errors.INVALID_SIGNATURE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER); } logger.debug("verified request"); if (samlObject instanceof AuthnRequestType) { @@ -216,7 +217,7 @@ public class SamlService { } else { event.event(EventType.LOGIN); event.error(Errors.INVALID_TOKEN); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST); } } @@ -230,7 +231,7 @@ public class SamlService { if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) { event.error(Errors.INVALID_SAML_AUTHN_REQUEST); event.detail(Details.REASON, "invalid_destination"); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST); } String bindingType = getBindingType(requestAbstractType); if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING))) bindingType = SamlProtocol.SAML_POST_BINDING; @@ -252,7 +253,7 @@ public class SamlService { if (redirect == null) { event.error(Errors.INVALID_REDIRECT_URI); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI ); } @@ -275,7 +276,7 @@ public class SamlService { } else { event.error(Errors.INVALID_SAML_AUTHN_REQUEST); event.detail(Details.REASON, "unsupported_nameid_format"); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unsupported NameIDFormat."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNSUPPORTED_NAME_ID_FORMAT); } } @@ -284,10 +285,10 @@ public class SamlService { // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?) HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event); - HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(); + HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers); if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse(); - LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo) + LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode()); // Attach state from SPNEGO authentication @@ -339,7 +340,7 @@ public class SamlService { if (!uriInfo.getAbsolutePath().equals(logoutRequest.getDestination())) { event.error(Errors.INVALID_SAML_LOGOUT_REQUEST); event.detail(Details.REASON, "invalid_destination"); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST); } // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways. @@ -366,7 +367,7 @@ public class SamlService { } } logger.debug("browser Logout"); - return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection); + return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers); } @@ -379,7 +380,7 @@ public class SamlService { if (redirectUri != null) { redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client); if (redirectUri == null) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI ); } } if (redirectUri != null) { @@ -391,7 +392,7 @@ public class SamlService { } private Response logout(UserSessionModel userSession) { - Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection); + Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers); if (response == null) event.user(userSession.getUser()).session(userSession).success(); return response; } diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java index 7bd4d033c8..0290771d7a 100755 --- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java @@ -7,6 +7,7 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.provider.Provider; import org.keycloak.services.managers.ClientSessionCode; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -21,6 +22,8 @@ public interface LoginProtocol extends Provider { LoginProtocol setUriInfo(UriInfo uriInfo); + LoginProtocol setHttpHeaders(HttpHeaders headers); + Response cancelLogin(ClientSessionModel clientSession); Response invalidSessionError(ClientSessionModel clientSession); Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java index 3900f1aaf3..433acd7846 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java @@ -33,6 +33,7 @@ import org.keycloak.protocol.LoginProtocol; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ResourceAdminManager; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; @@ -63,13 +64,17 @@ public class OIDCLoginProtocol implements LoginProtocol { protected UriInfo uriInfo; - public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo) { + protected HttpHeaders headers; + + public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers) { this.session = session; this.realm = realm; this.uriInfo = uriInfo; + this.headers = headers; } - public OIDCLoginProtocol() { + public OIDCLoginProtocol(){ + } @Override @@ -90,6 +95,12 @@ public class OIDCLoginProtocol implements LoginProtocol { return this; } + @Override + public OIDCLoginProtocol setHttpHeaders(HttpHeaders headers){ + this.headers = headers; + return this; + } + @Override public Response cancelLogin(ClientSessionModel clientSession) { String redirect = clientSession.getRedirectUri(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java index c2ec74bb4b..fd4b3b154d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java @@ -67,6 +67,9 @@ public class OIDCLoginProtocolService { @Context private KeycloakSession session; + @Context + private HttpHeaders headers; + public OIDCLoginProtocolService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) { this.realm = realm; this.tokenManager = new TokenManager(); @@ -226,7 +229,7 @@ public class OIDCLoginProtocolService { @Path("oauth/oob") @GET public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) { - LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo); + LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo, headers); if (code != null) { return forms.setClientSessionCode(code).createCode(); } else { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 07fd828c59..da9fdd83fa 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -30,6 +30,7 @@ import org.keycloak.representations.RefreshToken; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.util.Time; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriInfo; import java.io.IOException; import java.util.HashSet; @@ -73,7 +74,7 @@ public class TokenManager { } } - public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken) throws OAuthErrorException { + public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken, HttpHeaders headers) throws OAuthErrorException { UserModel user = session.users().getUserById(oldToken.getSubject(), realm); if (user == null) { throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user"); @@ -85,7 +86,7 @@ public class TokenManager { UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState()); if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.logout(session, realm, userSession, uriInfo, connection); + AuthenticationManager.logout(session, realm, userSession, uriInfo, connection, headers); throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active"); } ClientSessionModel clientSession = null; @@ -124,12 +125,12 @@ public class TokenManager { } - public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException { + public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException { RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken); event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId()); - TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken); + TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken, headers); // validate authorizedClient is same as validated client if (!validation.clientSession.getClient().getId().equals(authorizedClient.getId())) { throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token. Token client and authorized client don't match"); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 6f6441f55e..708fb2119b 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -24,6 +24,7 @@ import org.keycloak.services.ErrorPageException; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.HttpAuthenticationManager; +import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Urls; @@ -116,7 +117,7 @@ public class AuthorizationEndpoint { action = Action.REGISTER; if (!realm.isRegistrationAllowed()) { - throw new ErrorPageException(session, realm, uriInfo, "Registration not allowed"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED); } return this; @@ -148,21 +149,21 @@ public class AuthorizationEndpoint { private void checkSsl() { if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) { event.error(Errors.SSL_REQUIRED); - throw new ErrorPageException(session, realm, uriInfo, "HTTPS required"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); } } private void checkRealm() { if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - throw new ErrorPageException(session, realm, uriInfo, "Realm not enabled"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); } } private void checkClient() { if (clientId == null) { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, realm, uriInfo, "Missing paramater: " + OIDCLoginProtocol.CLIENT_ID_PARAM); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM ); } event.client(clientId); @@ -170,17 +171,17 @@ public class AuthorizationEndpoint { client = realm.findClient(clientId); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - throw new ErrorPageException(session, realm, uriInfo, "Client not found"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.CLIENT_NOT_FOUND ); } if ((client instanceof ApplicationModel) && ((ApplicationModel) client).isBearerOnly()) { event.error(Errors.NOT_ALLOWED); - throw new ErrorPageException(session, realm, uriInfo, "Bearer only clients are not allowed to initiate browser login"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.BEARER_ONLY ); } if (client.isDirectGrantsOnly()) { event.error(Errors.NOT_ALLOWED); - throw new ErrorPageException(session, realm, uriInfo, "Direct grants only clients are not allowed to initiate browser login"); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.DIRECT_GRANTS_ONLY); } } @@ -190,7 +191,7 @@ public class AuthorizationEndpoint { responseType = legacyResponseType; } else { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, realm, uriInfo, "Missing query parameter: " + OIDCLoginProtocol.RESPONSE_TYPE_PARAM); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM ); } } @@ -200,7 +201,7 @@ public class AuthorizationEndpoint { action = Action.CODE; } else { event.error(Errors.INVALID_REQUEST); - throw new ErrorPageException(session, realm, uriInfo, "Invalid " + OIDCLoginProtocol.RESPONSE_TYPE_PARAM); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM ); } } @@ -210,7 +211,7 @@ public class AuthorizationEndpoint { redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client); if (redirectUri == null) { event.error(Errors.INVALID_REDIRECT_URI); - throw new ErrorPageException(session, realm, uriInfo, "Invalid " + OIDCLoginProtocol.REDIRECT_URI_PARAM); + throw new ErrorPageException(session, realm, uriInfo, headers, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM); } } @@ -237,8 +238,8 @@ public class AuthorizationEndpoint { IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(idpHint); if (identityProviderModel == null) { - return Flows.forms(session, realm, null, uriInfo) - .setError("Could not find an identity provider with the identifier [" + idpHint + "].") + return Flows.forms(session, realm, null, uriInfo, headers) + .setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint) .createErrorPage(); } return buildRedirectToIdentityProvider(idpHint, accessCode); @@ -249,11 +250,11 @@ public class AuthorizationEndpoint { // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?) HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event); - HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(); + HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers); if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse(); if (prompt != null && prompt.equals("none")) { - OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo); + OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers); return oauth.cancelLogin(clientSession); } @@ -271,13 +272,13 @@ public class AuthorizationEndpoint { return buildRedirectToIdentityProvider(identityProviders.get(0).getId(), accessCode); } - return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + realm.getName() + "] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.IDENTITY_PROVIDER_NOT_UNIQUE, realm.getName()).createErrorPage(); } - return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + realm.getName() + "] does not support any credential type.").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.REALM_SUPPORTS_NO_CREDENTIALS, realm.getName()).createErrorPage(); } - LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo) + LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(accessCode); // Attach state from SPNEGO authentication @@ -306,7 +307,7 @@ public class AuthorizationEndpoint { private Response buildRegister() { authManager.expireIdentityCookie(realm, uriInfo, clientConnection); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode()) .createRegistration(); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 16ab80c043..a47aa6a2c1 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -21,6 +21,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils; import org.keycloak.representations.RefreshToken; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.flows.Flows; @@ -91,7 +92,7 @@ public class LogoutEndpoint { if (redirectUri != null) { String validatedRedirect = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm); if (validatedRedirect == null) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI); } return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build(); } else { @@ -143,7 +144,7 @@ public class LogoutEndpoint { } private void logout(UserSessionModel userSession) { - authManager.logout(session, realm, userSession, uriInfo, clientConnection); + authManager.logout(session, realm, userSession, uriInfo, clientConnection, headers); event.user(userSession.getUser()).session(userSession).success(); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index ab78e6c7a9..730e9be9a6 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -276,7 +276,7 @@ public class TokenEndpoint { AccessTokenResponse res; try { - res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event); + res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event, headers); } catch (OAuthErrorException e) { event.error(Errors.INVALID_TOKEN); throw new ErrorResponseException(e.getError(), e.getDescription(), Response.Status.BAD_REQUEST); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java index caef4361e0..8dad6fd490 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java @@ -20,10 +20,7 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import javax.ws.rs.core.*; import java.util.HashMap; import java.util.Map; @@ -43,6 +40,9 @@ public class ValidateTokenEndpoint { @Context private UriInfo uriInfo; + @Context + private HttpHeaders headers; + private TokenManager tokenManager; private RealmModel realm; private EventBuilder event; @@ -81,7 +81,7 @@ public class ValidateTokenEndpoint { event.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId()); try { - tokenManager.validateToken(session, uriInfo, clientConnection, realm, token); + tokenManager.validateToken(session, uriInfo, clientConnection, realm, token, headers); } catch (OAuthErrorException e) { Map error = new HashMap(); error.put(OAuth2Constants.ERROR, e.getError()); diff --git a/services/src/main/java/org/keycloak/services/ErrorPageException.java b/services/src/main/java/org/keycloak/services/ErrorPageException.java index 3f8d435ff0..634ea7a7ac 100644 --- a/services/src/main/java/org/keycloak/services/ErrorPageException.java +++ b/services/src/main/java/org/keycloak/services/ErrorPageException.java @@ -5,6 +5,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.services.resources.flows.Flows; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -16,18 +17,22 @@ public class ErrorPageException extends WebApplicationException { private final KeycloakSession session; private final RealmModel realm; private final UriInfo uriInfo; + private final HttpHeaders httpHeaders; private final String errorMessage; + private final Object[] parameters; - public ErrorPageException(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String errorMessage) { + public ErrorPageException(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String errorMessage, Object ... parameters) { this.session = session; this.realm = realm; this.uriInfo = uriInfo; + this.httpHeaders = headers; this.errorMessage = errorMessage; + this.parameters = parameters; } @Override public Response getResponse() { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, errorMessage); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, httpHeaders, errorMessage, parameters); } } diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java index e3bb56971a..9e0e4ab161 100755 --- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java @@ -42,7 +42,7 @@ public class AppAuthManager extends AuthenticationManager { public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { String tokenString = extractAuthorizationHeaderToken(headers); if (tokenString == null) return null; - AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString); + AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString, headers); return authResult; } diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index aa27f17a94..f37b83781d 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -79,7 +79,7 @@ public class AuthenticationManager { return userSession != null && userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime; } - public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { if (userSession == null) return; UserModel user = userSession.getUser(); userSession.setState(UserSessionModel.State.LOGGING_OUT); @@ -95,6 +95,7 @@ public class AuthenticationManager { if (authMethod == null) continue; // must be a keycloak service like account LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); protocol.backchannelLogout(userSession, clientSession); clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT); @@ -105,7 +106,7 @@ public class AuthenticationManager { } - public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { if (userSession == null) return null; UserModel user = userSession.getUser(); @@ -128,6 +129,7 @@ public class AuthenticationManager { if (authMethod == null) continue; // must be a keycloak service like account LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); try { logger.debugv("backchannel logout to: {0}", client.getClientId()); @@ -140,12 +142,13 @@ public class AuthenticationManager { } if (redirectClients.size() == 0) { - return finishBrowserLogout(session, realm, userSession, uriInfo, connection); + return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers); } for (ClientSessionModel nextRedirectClient : redirectClients) { String authMethod = nextRedirectClient.getAuthMethod(); LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); // setting this to logged out cuz I"m not sure protocols can always verify that the client was logged out or not nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT); @@ -161,16 +164,17 @@ public class AuthenticationManager { } } - return finishBrowserLogout(session, realm, userSession, uriInfo, connection); + return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers); } - protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) { + protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) { expireIdentityCookie(realm, uriInfo, connection); expireRememberMeCookie(realm, uriInfo, connection); userSession.setState(UserSessionModel.State.LOGGED_OUT); String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL); LoginProtocol protocol = session.getProvider(LoginProtocol.class, method); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); Response response = protocol.finishLogout(userSession); session.sessions().removeUserSession(realm, userSession); @@ -285,7 +289,7 @@ public class AuthenticationManager { } String tokenString = cookie.getValue(); - AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString); + AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString, headers); if (authResult == null) { expireIdentityCookie(realm, uriInfo, connection); return null; @@ -335,6 +339,7 @@ public class AuthenticationManager { if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(request.getHttpHeaders()) .setUriInfo(uriInfo); return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession)); @@ -364,7 +369,7 @@ public class AuthenticationManager { UserModel.RequiredAction action = user.getRequiredActions().iterator().next(); accessCode.setRequiredAction(action); - LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user); + LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()).setClientSessionCode(accessCode.getCode()).setUser(user); if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) { event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success(); LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); @@ -387,7 +392,7 @@ public class AuthenticationManager { } } - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()) .setClientSessionCode(accessCode.getCode()) .setAccessRequest(realmRoles, resourceRoles) .setClient(client) @@ -415,7 +420,7 @@ public class AuthenticationManager { } } - protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString) { + protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString, HttpHeaders headers) { try { AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive); if (checkActive) { @@ -435,7 +440,7 @@ public class AuthenticationManager { UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState()); if (!isSessionValid(realm, userSession)) { - if (userSession != null) logout(session, realm, userSession, uriInfo, connection); + if (userSession != null) logout(session, realm, userSession, uriInfo, connection, headers); logger.debug("User session not active"); return null; } diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java index 7dca9c1482..702b684aba 100644 --- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java @@ -58,7 +58,7 @@ public class HttpAuthenticationManager { } - public HttpAuthOutput spnegoAuthenticate() { + public HttpAuthOutput spnegoAuthenticate(HttpHeaders headers) { boolean kerberosSupported = false; for (RequiredCredentialModel c : realm.getRequiredCredentials()) { if (c.getType().equals(CredentialRepresentation.KERBEROS)) { @@ -96,7 +96,7 @@ public class HttpAuthenticationManager { CredentialValidationOutput output = session.users().validCredentials(realm, spnegoCredential); if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) { - return sendResponse(output.getAuthenticatedUser(), output.getState(), "spnego"); + return sendResponse(output.getAuthenticatedUser(), output.getState(), "spnego", headers); } else { String spnegoResponseToken = (String) output.getState().get(KerberosConstants.RESPONSE_TOKEN); return challengeNegotiation(spnegoResponseToken); @@ -106,7 +106,7 @@ public class HttpAuthenticationManager { // Send response after successful authentication - private HttpAuthOutput sendResponse(UserModel user, Map authState, String authMethod) { + private HttpAuthOutput sendResponse(UserModel user, Map authState, String authMethod, HttpHeaders headers) { if (logger.isTraceEnabled()) { logger.trace("User " + user.getUsername() + " authenticated with " + authMethod); } @@ -114,7 +114,7 @@ public class HttpAuthenticationManager { Response response; if (!user.isEnabled()) { event.error(Errors.USER_DISABLED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, Messages.ACCOUNT_DISABLED); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.ACCOUNT_DISABLED); } else { UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false); diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java index 0ad03522f8..a1cc072e9b 100755 --- a/services/src/main/java/org/keycloak/services/messages/Messages.java +++ b/services/src/main/java/org/keycloak/services/messages/Messages.java @@ -26,67 +26,151 @@ package org.keycloak.services.messages; */ public class Messages { - public static final String ACCOUNT_DISABLED = "accountDisabled"; - public static final String ACCOUNT_TEMPORARILY_DISABLED = "accountTemporarilyDisabled"; + public static final String INVALID_USER = "invalidUserMessage"; - public static final String INVALID_PASSWORD = "invalidPassword"; + public static final String INVALID_EMAIL = "invalidEmailMessage"; - public static final String INVALID_PASSWORD_EXISTING = "invalidPasswordExisting"; + public static final String ACCOUNT_DISABLED = "accountDisabledMessage"; - public static final String INVALID_PASSWORD_CONFIRM = "invalidPasswordConfirm"; + public static final String ACCOUNT_TEMPORARILY_DISABLED = "accountTemporarilyDisabledMessage"; - public static final String INVALID_EMAIL = "invalidEmail"; + public static final String EXPIRED_CODE = "expiredCodeMessage"; - public static final String INVALID_USER = "invalidUser"; + public static final String MISSING_FIRST_NAME = "missingFirstNameMessage"; - public static final String EXPIRED_CODE = "expiredCode"; + public static final String MISSING_LAST_NAME = "missingLastNameMessage"; - public static final String READ_ONLY_USER = "readOnlyUser"; + public static final String MISSING_EMAIL = "missingEmailMessage"; - public static final String READ_ONLY_PASSWORD = "readOnlyPassword"; + public static final String MISSING_USERNAME = "missingUsernameMessage"; - public static final String MISSING_EMAIL = "missingEmail"; + public static final String MISSING_PASSWORD = "missingPasswordMessage"; - public static final String MISSING_FIRST_NAME = "missingFirstName"; + public static final String MISSING_TOTP = "missingTotpMessage"; - public static final String MISSING_LAST_NAME = "missingLastName"; + public static final String NOTMATCH_PASSWORD = "notMatchPasswordMessage"; - public static final String MISSING_PASSWORD = "missingPassword"; + public static final String INVALID_PASSWORD_EXISTING = "invalidPasswordExistingMessage"; - public static final String NOTMATCH_PASSWORD = "notMatchPassword"; + public static final String INVALID_PASSWORD_CONFIRM = "invalidPasswordConfirmMessage"; - public static final String MISSING_USERNAME = "missingUsername"; + public static final String INVALID_TOTP = "invalidTotpMessage"; - public static final String MISSING_TOTP = "missingTotp"; + public static final String USERNAME_EXISTS = "usernameExistsMessage"; - public static final String INVALID_TOTP = "invalidTotp"; + public static final String EMAIL_EXISTS = "emailExistsMessage"; - public static final String USERNAME_EXISTS = "usernameExists"; + public static final String FEDERATED_IDENTITY_EMAIL_EXISTS = "federatedIdentityEmailExistsMessage"; - public static final String EMAIL_EXISTS = "emailExists"; + public static final String FEDERATED_IDENTITY_USERNAME_EXISTS = "federatedIdentityUsernameExistsMessage"; - public static final String ACTION_WARN_TOTP = "actionTotpWarning"; + public static final String CONFIGURE_TOTP = "configureTotpMessage"; - public static final String ACTION_WARN_PROFILE = "actionProfileWarning"; + public static final String UPDATE_PROFILE = "updateProfileMessage"; - public static final String ACTION_WARN_PASSWD = "actionPasswordWarning"; + public static final String UPDATE_PASSWORD = "updatePasswordMessage"; - public static final String ACTION_WARN_EMAIL = "actionEmailWarning"; + public static final String VERIFY_EMAIL = "verifyEmailMessage"; - public static final String MISSING_IDENTITY_PROVIDER = "missingIdentityProvider"; + public static final String EMAIL_VERIFIED = "emailVerifiedMessage"; - public static final String INVALID_FEDERATED_IDENTITY_ACTION = "invalidFederatedIdentityAction"; + public static final String EMAIL_SENT = "emailSentMessage"; - public static final String IDENTITY_PROVIDER_NOT_FOUND = "identityProviderNotFound"; + public static final String EMAIL_SENT_ERROR = "emailSendErrorMessage"; - public static final String FEDERATED_IDENTITY_NOT_ACTIVE = "federatedIdentityLinkNotActive"; + public static final String ACCOUNT_UPDATED = "accountUpdatedMessage"; - public static final String FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER = "federatedIdentityRemovingLastProvider"; + public static final String ACCOUNT_PASSWORD_UPDATED = "accountPasswordUpdatedMessage"; - public static final String IDENTITY_PROVIDER_REDIRECT_ERROR = "identityProviderRedirectError"; + public static final String NO_ACCESS = "noAccessMessage"; - public static final String IDENTITY_PROVIDER_REMOVED = "identityProviderRemoved"; + public static final String FAILED_TO_PROCESS_RESPONSE = "failedToProcessResponseMessage"; - public static final String ERROR = "error"; + public static final String HTTPS_REQUIRED = "httpsRequiredMessage"; + public static final String REALM_NOT_ENABLED = "realmNotEnabledMessage"; + + public static final String INVALID_REQUEST = "invalidRequestMessage"; + + public static final String INVALID_REQUESTER = "invalidRequesterMessage"; + + public static final String UNKNOWN_LOGIN_REQUESTER = "unknownLoginRequesterMessage"; + + public static final String LOGIN_REQUESTER_NOT_ENABLED = "loginRequesterNotEnabledMessage"; + + public static final String BEARER_ONLY = "bearerOnlyMessage"; + + public static final String DIRECT_GRANTS_ONLY = "directGrantsOnlyMessage"; + + public static final String INVALID_REDIRECT_URI = "invalidRedirectUriMessage"; + + public static final String UNSUPPORTED_NAME_ID_FORMAT = "unsupportedNameIdFormatMessage"; + + public static final String REGISTRATION_NOT_ALLOWED = "registrationNotAllowedMessage"; + + public static final String PERMISSION_NOT_APPROVED = "permissionNotApprovedMessage"; + + public static final String NO_RELAY_STATE_IN_RESPONSE = "noRelayStateInResponseMessage"; + + public static final String IDENTITY_PROVIDER_ALREADY_LINKED = "identityProviderAlreadyLinkedMessage"; + + public static final String INSUFFICIENT_PERMISSION = "insufficientPermissionMessage"; + + public static final String COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST = "couldNotProceedWithAuthenticationRequestMessage"; + + public static final String COULD_NOT_OBTAIN_TOKEN = "couldNotObtainTokenMessage"; + + public static final String UNEXPECTED_ERROR_RETRIEVING_TOKEN = "unexpectedErrorRetrievingTokenMessage"; + + public static final String IDENTITY_PROVIDER_AUTHENTICATION_FAILED = "identityProviderAuthenticationFailedMessage"; + + public static final String UNEXPECTED_ERROR_HANDLING_RESPONSE = "unexpectedErrorHandlingResponseMessage"; + + public static final String COULD_NOT_SEND_AUTHENTICATION_REQUEST = "couldNotSendAuthenticationRequestMessage"; + + public static final String UNEXPECTED_ERROR_HANDLING_REQUEST = "unexpectedErrorHandlingRequestMessage"; + + public static final String INVALID_ACCESS_CODE = "invalidAccessCodeMessage"; + + public static final String SESSION_NOT_ACTIVE = "sessionNotActiveMessage"; + + public static final String UNKNOWN_CODE = "unknownCodeMessage"; + + public static final String INVALID_CODE = "invalidCodeMessage"; + + public static final String IDENTITY_PROVIDER_UNEXPECTED_ERROR = "identityProviderUnexpectedErrorMessage"; + + public static final String IDENTITY_PROVIDER_NOT_FOUND = "identityProviderNotFoundMessage"; + + public static final String IDENTITY_PROVIDER_NOT_UNIQUE = "identityProviderNotUniqueMessage"; + + public static final String REALM_SUPPORTS_NO_CREDENTIALS = "realmSupportsNoCredentialsMessage"; + + public static final String READ_ONLY_USER = "readOnlyUserMessage"; + + public static final String READ_ONLY_PASSWORD = "readOnlyPasswordMessage"; + + public static final String SUCCESS_TOTP_REMOVED = "successTotpRemovedMessage"; + + public static final String SUCCESS_TOTP = "successTotpMessage"; + + public static final String MISSING_IDENTITY_PROVIDER = "missingIdentityProviderMessage"; + + public static final String INVALID_FEDERATED_IDENTITY_ACTION = "invalidFederatedIdentityActionMessage"; + + public static final String FEDERATED_IDENTITY_NOT_ACTIVE = "federatedIdentityLinkNotActiveMessage"; + + public static final String FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER = "federatedIdentityRemovingLastProviderMessage"; + + public static final String IDENTITY_PROVIDER_REDIRECT_ERROR = "identityProviderRedirectErrorMessage"; + + public static final String IDENTITY_PROVIDER_REMOVED = "identityProviderRemovedMessage"; + + public static final String MISSING_PARAMETER = "missingParameterMessage"; + + public static final String CLIENT_NOT_FOUND = "clientNotFoundMessage"; + + public static final String INVALID_PARAMETER = "invalidParameterMessage"; + + public static final String FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING = "federatedIdentityRegistrationEmailMissingMessage"; } diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 8b5297b298..1efc33a032 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -32,20 +32,7 @@ import org.keycloak.events.Event; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventStoreProvider; import org.keycloak.events.EventType; -import org.keycloak.models.AccountRoles; -import org.keycloak.models.ApplicationModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.Constants; -import org.keycloak.models.FederatedIdentityModel; -import org.keycloak.models.IdentityProviderModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelReadOnlyException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserCredentialValueModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserSessionModel; +import org.keycloak.models.*; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.oidc.OIDCLoginProtocol; @@ -160,7 +147,7 @@ public class AccountService { public void init() { eventStore = session.getProvider(EventStoreProvider.class); - account = session.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo); + account = session.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo).setHttpHeaders(headers); AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers); if (authResult != null) { @@ -242,7 +229,7 @@ public class AccountService { try { require(AccountRoles.MANAGE_ACCOUNT); } catch (ForbiddenException e) { - return Flows.forms(session, realm, null, uriInfo).setError("No access").createErrorPage(); + return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.NO_ACCESS).createErrorPage(); } setReferrerOnPage(); @@ -442,7 +429,7 @@ public class AccountService { event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success(); } setReferrerOnPage(); - return account.setSuccess("accountUpdated").createResponse(AccountPages.ACCOUNT); + return account.setSuccess(Messages.ACCOUNT_UPDATED).createResponse(AccountPages.ACCOUNT); } catch (ModelReadOnlyException roe) { setReferrerOnPage(); return account.setError(Messages.READ_ONLY_USER).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT); @@ -466,7 +453,7 @@ public class AccountService { event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success(); setReferrerOnPage(); - return account.setSuccess("successTotpRemoved").createResponse(AccountPages.TOTP); + return account.setSuccess(Messages.SUCCESS_TOTP_REMOVED).createResponse(AccountPages.TOTP); } @@ -545,7 +532,7 @@ public class AccountService { event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success(); setReferrerOnPage(); - return account.setSuccess("successTotp").createResponse(AccountPages.TOTP); + return account.setSuccess(Messages.SUCCESS_TOTP).createResponse(AccountPages.TOTP); } /** @@ -614,7 +601,11 @@ public class AccountService { } catch (ModelReadOnlyException mre) { setReferrerOnPage(); return account.setError(Messages.READ_ONLY_PASSWORD).createResponse(AccountPages.PASSWORD); - } catch (Exception ape) { + }catch (ModelException me) { + logger.error("Failed to update password", me); + setReferrerOnPage(); + return account.setError(me.getMessage(), me.getParameters()).createResponse(AccountPages.PASSWORD); + }catch (Exception ape) { logger.error("Failed to update password", ape); setReferrerOnPage(); return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD); @@ -631,7 +622,7 @@ public class AccountService { event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success(); setReferrerOnPage(); - return account.setPasswordSet(true).setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD); + return account.setPasswordSet(true).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createResponse(AccountPages.PASSWORD); } @Path("federated-identity-update") diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index c95b61807f..4666ff4afa 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -46,6 +46,7 @@ import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager.AuthResult; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.EventsManager; +import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.flows.Flows; import org.keycloak.services.resources.flows.Urls; import org.keycloak.social.SocialIdentityProvider; @@ -57,13 +58,8 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; +import javax.ws.rs.core.*; import javax.ws.rs.core.Response.Status; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -97,6 +93,10 @@ public class IdentityBrokerService { @Context private HttpRequest request; + + @Context + private HttpHeaders headers; + private EventBuilder event; public IdentityBrokerService(RealmModel realmModel) { @@ -135,12 +135,12 @@ public class IdentityBrokerService { return response; } } catch (IdentityBrokerException e) { - return redirectToErrorPage("Could not send authentication request to identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId); } catch (Exception e) { - return redirectToErrorPage("Unexpected error when handling authentication request to identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId); } - return redirectToErrorPage("Could not proceed with authentication request to identity provider."); + return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST); } @GET @@ -191,11 +191,10 @@ public class IdentityBrokerService { } if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) { - return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo) + return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo, headers) .setClientSessionCode(authManager.extractAuthorizationHeaderToken(this.request.getHttpHeaders())) .setAccessRequest("Your information from " + providerId + " identity provider.") .setClient(clientModel) - .setUriInfo(this.uriInfo) .setActionUri(this.uriInfo.getRequestUri()) .createOAuthGrant(null), clientModel); } @@ -220,9 +219,9 @@ public class IdentityBrokerService { return badRequest("Invalid token."); } catch (IdentityBrokerException e) { - return redirectToErrorPage("Could not obtain token fron identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.COULD_NOT_OBTAIN_TOKEN, e, providerId); } catch (Exception e) { - return redirectToErrorPage("Unexpected error when retrieving token from identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.UNEXPECTED_ERROR_RETRIEVING_TOKEN, e, providerId); } } @@ -232,7 +231,7 @@ public class IdentityBrokerService { public Response consentTokenRetrieval(@PathParam("provider_id") String providerId, MultivaluedMap formData) { if (formData.containsKey("cancel")) { - return redirectToErrorPage("Permission not approved."); + return redirectToErrorPage(Messages.PERMISSION_NOT_APPROVED); } return getToken(providerId, true); @@ -251,7 +250,7 @@ public class IdentityBrokerService { String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null)); if (relayState == null) { - return redirectToErrorPage("No relay state in response from identity identity [" + providerId + "."); + return redirectToErrorPage(Messages.NO_RELAY_STATE_IN_RESPONSE, providerId); } if (isDebugEnabled()) { @@ -287,10 +286,10 @@ public class IdentityBrokerService { return performLocalAuthentication(identity, clientSessionCode); } catch (IdentityBrokerException e) { rollback(); - return redirectToErrorPage("Authentication failed. Could not authenticate with identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.IDENTITY_PROVIDER_AUTHENTICATION_FAILED, e, providerId); } catch (Exception e) { rollback(); - return redirectToErrorPage("Unexpected error when handling response from identity provider [" + providerId + "].", e); + return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE, e, providerId); } finally { if (this.session.getTransaction().isActive()) { this.session.getTransaction().commit(); @@ -353,7 +352,7 @@ public class IdentityBrokerService { this.event.event(EventType.IDENTITY_PROVIDER_ACCCOUNT_LINKING); if (federatedUser != null) { - return redirectToErrorPage("The identity returned by the identity provider [" + providerId + "] is already linked to other user."); + return redirectToErrorPage(Messages.IDENTITY_PROVIDER_ALREADY_LINKED, providerId); } UserModel authenticatedUser = clientSession.getUserSession().getUser(); @@ -364,12 +363,12 @@ public class IdentityBrokerService { if (!authenticatedUser.isEnabled()) { fireErrorEvent(Errors.USER_DISABLED); - return redirectToErrorPage("User is disabled."); + return redirectToErrorPage(Messages.ACCOUNT_DISABLED); } if (!authenticatedUser.hasRole(this.realmModel.getApplicationByName(ACCOUNT_MANAGEMENT_APP).getRole(MANAGE_ACCOUNT))) { fireErrorEvent(Errors.NOT_ALLOWED); - return redirectToErrorPage("Insufficient permissions to link identities."); + return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION); } this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, federatedIdentityModel); @@ -438,28 +437,28 @@ public class IdentityBrokerService { return Urls.identityProviderAuthnResponse(this.uriInfo.getBaseUri(), providerId, this.realmModel.getName()).toString(); } - private Response redirectToErrorPage(String message) { - return redirectToErrorPage(message, null); + private Response redirectToErrorPage(String message, Object ... parameters) { + return redirectToErrorPage(message, null, parameters); } - private Response redirectToErrorPage(String message, Throwable throwable) { + private Response redirectToErrorPage(String message, Throwable throwable, Object ... parameters) { if (message == null) { - message = "Unexpected error when authenticating with identity provider"; + message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR; } fireErrorEvent(message, throwable); - return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, message); + return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, headers, message, parameters); } private Response redirectToLoginPage(Throwable t, ClientSessionCode clientCode) { String message = t.getMessage(); if (message == null) { - message = "Unexpected error when authenticating with identity provider"; + message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR; } fireErrorEvent(message); - return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo) + return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo, headers) .setClientSessionCode(clientCode.getCode()) .setError(message) .createLogin(); @@ -535,7 +534,7 @@ public class IdentityBrokerService { if (existingUser != null) { fireErrorEvent(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS); - throw new IdentityBrokerException("federatedIdentityEmailExists"); + throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_EMAIL_EXISTS); } String username = updatedIdentity.getUsername(); @@ -543,7 +542,7 @@ public class IdentityBrokerService { username = updatedIdentity.getEmail(); if (username == null || username.trim().length() == 0) { fireErrorEvent(Errors.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING); - throw new IdentityBrokerException("federatedIdentityRegistrationEmailMissing"); + throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING); // TODO KEYCLOAK-1053 (ask user to enter email address) should be implemented instead of plain exception as better solution for this case } username = username.trim(); @@ -555,7 +554,7 @@ public class IdentityBrokerService { if (existingUser != null) { fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS); - throw new IdentityBrokerException("federatedIdentityUsernameExists"); + throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_USERNAME_EXISTS); } if (isDebugEnabled()) { diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 0198644bd7..9e9282aa4a 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -32,15 +32,8 @@ import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.login.LoginFormsProvider; -import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; +import org.keycloak.models.*; import org.keycloak.models.UserModel.RequiredAction; -import org.keycloak.models.UserSessionModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.TimeBasedOTP; import org.keycloak.protocol.LoginProtocol; @@ -160,7 +153,7 @@ public class LoginActionsService { return false; } else if (!clientCode.isValid(requiredAction)) { event.error(Errors.INVALID_CODE); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE); return false; } else { return true; @@ -172,7 +165,7 @@ public class LoginActionsService { return false; } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) { event.error(Errors.INVALID_CODE); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.INVALID_CODE); return false; } else { return true; @@ -182,18 +175,18 @@ public class LoginActionsService { public boolean check(String code) { if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); return false; } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); return false; } clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE); return false; } return true; @@ -224,7 +217,7 @@ public class LoginActionsService { clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE); } - LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo) + LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(clientSessionCode.getCode()); return forms.createLogin(); @@ -242,7 +235,7 @@ public class LoginActionsService { event.event(EventType.REGISTER); if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED); } Checks checks = new Checks(); @@ -256,7 +249,7 @@ public class LoginActionsService { authManager.expireIdentityCookie(realm, uriInfo, clientConnection); - return Flows.forms(session, realm, clientSession.getClient(), uriInfo) + return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers) .setClientSessionCode(clientSessionCode.getCode()) .createRegistration(); } @@ -276,17 +269,17 @@ public class LoginActionsService { event.event(EventType.LOGIN); if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); } ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE); } ClientSessionModel clientSession = clientCode.getClientSession(); @@ -295,7 +288,7 @@ public class LoginActionsService { if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) { clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE); event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE); - return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.EXPIRED_CODE) + return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo, headers).setError(Messages.EXPIRED_CODE) .setClientSessionCode(clientCode.getCode()) .createLogin(); } @@ -319,17 +312,18 @@ public class LoginActionsService { ClientModel client = clientSession.getClient(); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER); } if (!client.isEnabled()) { event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED); } if (formData.containsKey("cancel")) { event.error(Errors.REJECTED_BY_USER); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); return protocol.cancelLogin(clientSession); } @@ -356,14 +350,14 @@ public class LoginActionsService { return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event); case ACCOUNT_TEMPORARILY_DISABLED: event.error(Errors.USER_TEMPORARILY_DISABLED); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); case ACCOUNT_DISABLED: event.error(Errors.USER_DISABLED); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setError(Messages.ACCOUNT_DISABLED) .setClientSessionCode(clientCode.getCode()) .setFormData(formData).createLogin(); @@ -373,19 +367,19 @@ public class LoginActionsService { String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey()); formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken); - return Flows.forms(this.session, realm, client, uriInfo) + return Flows.forms(this.session, realm, client, uriInfo, headers) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLoginTotp(); case INVALID_USER: event.error(Errors.USER_NOT_FOUND); - return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER) + return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); default: event.error(Errors.INVALID_USER_CREDENTIALS); - return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER) + return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createLogin(); @@ -407,25 +401,25 @@ public class LoginActionsService { event.event(EventType.REGISTER); if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); } if (!realm.isRegistrationAllowed()) { event.error(Errors.REGISTRATION_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED); } ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm); if (clientCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE); } if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE); } String username = formData.getFirst("username"); @@ -444,17 +438,17 @@ public class LoginActionsService { if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); } ClientModel client = clientSession.getClient(); if (client == null) { event.error(Errors.CLIENT_NOT_FOUND); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER); } if (!client.isEnabled()) { event.error(Errors.CLIENT_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED); } @@ -464,15 +458,20 @@ public class LoginActionsService { } // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm - String error = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes); - if (error == null) { - error = Validation.validatePassword(formData, realm.getPasswordPolicy()); + String errorMessage = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes); + Object[] parameters = new Object[0]; + if (errorMessage == null) { + PasswordPolicy.Error error = Validation.validatePassword(formData, realm.getPasswordPolicy()); + if(error != null){ + errorMessage = error.getMessage(); + parameters = error.getParameters(); + } } - if (error != null) { + if (errorMessage != null) { event.error(Errors.INVALID_REGISTRATION); - return Flows.forms(session, realm, client, uriInfo) - .setError(error) + return Flows.forms(session, realm, client, uriInfo, headers) + .setError(errorMessage, parameters) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) .createRegistration(); @@ -481,7 +480,7 @@ public class LoginActionsService { // Validate that user with this username doesn't exist in realm or any federation provider if (session.users().getUserByUsername(username, realm) != null) { event.error(Errors.USERNAME_IN_USE); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setError(Messages.USERNAME_EXISTS) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) @@ -491,7 +490,7 @@ public class LoginActionsService { // Validate that user with this email doesn't exist in realm or any federation provider if (session.users().getUserByEmail(email, realm) != null) { event.error(Errors.EMAIL_IN_USE); - return Flows.forms(session, realm, client, uriInfo) + return Flows.forms(session, realm, client, uriInfo, headers) .setError(Messages.EMAIL_EXISTS) .setFormData(formData) .setClientSessionCode(clientCode.getCode()) @@ -512,9 +511,14 @@ public class LoginActionsService { boolean passwordUpdateSuccessful; String passwordUpdateError = null; + Object[] passwordUpdateErrorParameters = null; try { session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password"))); passwordUpdateSuccessful = true; + } catch (ModelException me) { + passwordUpdateSuccessful = false; + passwordUpdateError = me.getMessage(); + passwordUpdateErrorParameters = me.getParameters(); } catch (Exception ape) { passwordUpdateSuccessful = false; passwordUpdateError = ape.getMessage(); @@ -523,8 +527,8 @@ public class LoginActionsService { // User already registered, but force him to update password if (!passwordUpdateSuccessful) { user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); - return Flows.forms(session, realm, client, uriInfo) - .setError(passwordUpdateError) + return Flows.forms(session, realm, client, uriInfo, headers) + .setError(passwordUpdateError, passwordUpdateErrorParameters) .setClientSessionCode(clientCode.getCode()) .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD); } @@ -552,7 +556,7 @@ public class LoginActionsService { if (!checkSsl()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); } String code = formData.getFirst("code"); @@ -560,7 +564,7 @@ public class LoginActionsService { ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid access code."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_ACCESS_CODE); } ClientSessionModel clientSession = accessCode.getClientSession(); event.detail(Details.CODE_ID, clientSession.getId()); @@ -582,14 +586,15 @@ public class LoginActionsService { } if (!AuthenticationManager.isSessionValid(realm, userSession)) { - AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection); + AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection, headers); event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Session not active"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE); } event.session(userSession); LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod()); protocol.setRealm(realm) + .setHttpHeaders(headers) .setUriInfo(uriInfo); if (formData.containsKey("cancel")) { event.error(Errors.REJECTED_BY_USER); @@ -623,7 +628,7 @@ public class LoginActionsService { String error = Validation.validateUpdateProfileForm(formData); if (error != null) { - return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error) + return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(error) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PROFILE); } @@ -641,7 +646,7 @@ public class LoginActionsService { // check for duplicated email if (userByEmail != null && !userByEmail.getId().equals(user.getId())) { - return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(Messages.EMAIL_EXISTS) + return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(Messages.EMAIL_EXISTS) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PROFILE); } @@ -680,7 +685,7 @@ public class LoginActionsService { String totp = formData.getFirst("totp"); String totpSecret = formData.getFirst("totpSecret"); - LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user); + LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user); if (Validation.isEmpty(totp)) { return loginForms.setError(Messages.MISSING_TOTP) .setClientSessionCode(accessCode.getCode()) @@ -725,7 +730,7 @@ public class LoginActionsService { String passwordNew = formData.getFirst("password-new"); String passwordConfirm = formData.getFirst("password-confirm"); - LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user); + LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user); if (Validation.isEmpty(passwordNew)) { return loginForms.setError(Messages.MISSING_PASSWORD) .setClientSessionCode(accessCode.getCode()) @@ -738,6 +743,10 @@ public class LoginActionsService { try { session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew)); + } catch (ModelException me) { + return loginForms.setError(me.getMessage(), me.getParameters()) + .setClientSessionCode(accessCode.getCode()) + .createResponse(RequiredAction.UPDATE_PASSWORD); } catch (Exception ape) { return loginForms.setError(ape.getMessage()) .setClientSessionCode(accessCode.getCode()) @@ -751,7 +760,7 @@ public class LoginActionsService { if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) { String actionCookieValue = getActionCookie(); if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) { - return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setSuccess("passwordUpdated").createInfoPage(); + return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createInfoPage(); } } @@ -783,7 +792,7 @@ public class LoginActionsService { String actionCookieValue = getActionCookie(); if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) { - return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setSuccess("emailVerified").createInfoPage(); + return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.EMAIL_VERIFIED).createInfoPage(); } event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN); @@ -801,7 +810,7 @@ public class LoginActionsService { createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(accessCode.getCode()) .setUser(userSession.getUser()) .createResponse(RequiredAction.VERIFY_EMAIL); @@ -818,11 +827,11 @@ public class LoginActionsService { return checks.response; } ClientSessionCode accessCode = checks.clientCode; - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(accessCode.getCode()) .createResponse(RequiredAction.UPDATE_PASSWORD); } else { - return Flows.forms(session, realm, null, uriInfo) + return Flows.forms(session, realm, null, uriInfo, headers) .setClientSessionCode(code) .createPasswordReset(); } @@ -835,16 +844,16 @@ public class LoginActionsService { final MultivaluedMap formData) { event.event(EventType.SEND_RESET_PASSWORD); if (!checkSsl()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required"); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED); } if (!realm.isEnabled()) { event.error(Errors.REALM_DISABLED); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED); } ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); if (accessCode == null) { event.error(Errors.INVALID_CODE); - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE); } ClientSessionModel clientSession = accessCode.getClientSession(); @@ -852,12 +861,10 @@ public class LoginActionsService { ClientModel client = clientSession.getClient(); if (client == null) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, - "Unknown login requester."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER); } if (!client.isEnabled()) { - return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, - "Login requester not enabled."); + return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED); } event.client(client.getClientId()) @@ -900,7 +907,7 @@ public class LoginActionsService { } catch (EmailException e) { event.error(Errors.EMAIL_SEND_FAILED); logger.error("Failed to send password reset email", e); - return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError") + return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.EMAIL_SENT_ERROR) .setClientSessionCode(accessCode.getCode()) .createErrorPage(); } @@ -908,7 +915,7 @@ public class LoginActionsService { createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); } - return Flows.forms(session, realm, client, uriInfo).setSuccess("emailSent").setClientSessionCode(accessCode.getCode()).createPasswordReset(); + return Flows.forms(session, realm, client, uriInfo, headers).setSuccess(Messages.EMAIL_SENT).setClientSessionCode(accessCode.getCode()).createPasswordReset(); } private String getActionCookie() { diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java index 8c9e4d4b65..3d0697db8a 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java @@ -26,6 +26,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -37,16 +38,16 @@ public class Flows { private Flows() { } - public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo) { - return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client); + public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo, HttpHeaders headers) { + return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client).setHttpHeaders(headers); } public static ErrorFlows errors() { return new ErrorFlows(); } - public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String message) { - return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage(); + public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String message, Object ... parameters) { + return Flows.forms(session, realm, null, uriInfo, headers).setError(message,parameters).createErrorPage(); } diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java index 7889388fc9..ab56f4d7db 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java @@ -190,6 +190,10 @@ public class Urls { return requiredActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId); } + public static String localeCookiePath(URI baseUri, String realmName){ + return realmBase(baseUri).path(realmName).build().getRawPath(); + } + public static URI themeRoot(URI baseUri) { return themeBase(baseUri).path(Version.RESOURCES_VERSION).build(); } diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java index d306dc050d..f72a63023a 100755 --- a/services/src/main/java/org/keycloak/services/validation/Validation.java +++ b/services/src/main/java/org/keycloak/services/validation/Validation.java @@ -48,7 +48,7 @@ public class Validation { return null; } - public static String validatePassword(MultivaluedMap formData, PasswordPolicy policy) { + public static PasswordPolicy.Error validatePassword(MultivaluedMap formData, PasswordPolicy policy) { return policy.validate(formData.getFirst("username"), formData.getFirst("password")); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java index 6497cb50f1..4743826502 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -283,7 +283,7 @@ public class AccountTest { // All fields are required, so there should be an error when something is missing. profilePage.updateProfile("", "New last", "new@email.com"); - Assert.assertEquals("Please specify first name", profilePage.getError()); + Assert.assertEquals("Please specify first name.", profilePage.getError()); Assert.assertEquals("", profilePage.getFirstName()); Assert.assertEquals("New last", profilePage.getLastName()); Assert.assertEquals("new@email.com", profilePage.getEmail()); @@ -292,7 +292,7 @@ public class AccountTest { profilePage.updateProfile("New first", "", "new@email.com"); - Assert.assertEquals("Please specify last name", profilePage.getError()); + Assert.assertEquals("Please specify last name.", profilePage.getError()); Assert.assertEquals("New first", profilePage.getFirstName()); Assert.assertEquals("", profilePage.getLastName()); Assert.assertEquals("new@email.com", profilePage.getEmail()); @@ -301,7 +301,7 @@ public class AccountTest { profilePage.updateProfile("New first", "New last", ""); - Assert.assertEquals("Please specify email", profilePage.getError()); + Assert.assertEquals("Please specify email.", profilePage.getError()); Assert.assertEquals("New first", profilePage.getFirstName()); Assert.assertEquals("New last", profilePage.getLastName()); Assert.assertEquals("", profilePage.getEmail()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java index 1d6665171b..0d6b4cfefc 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java @@ -236,7 +236,7 @@ public class RequiredActionEmailVerificationTest { events.expectRequiredAction(EventType.VERIFY_EMAIL).session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent(); assertTrue(infoPage.isCurrent()); - assertEquals("Email verified", infoPage.getInfo()); + assertEquals("Your email address has been verified.", infoPage.getInfo()); loginPage.open(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java index d832f967ee..e5e4c3c3f3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java @@ -108,7 +108,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - Assert.assertEquals("Please specify first name", updateProfilePage.getError()); + Assert.assertEquals("Please specify first name.", updateProfilePage.getError()); events.assertEmpty(); } @@ -125,7 +125,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - Assert.assertEquals("Please specify last name", updateProfilePage.getError()); + Assert.assertEquals("Please specify last name.", updateProfilePage.getError()); events.assertEmpty(); } @@ -142,7 +142,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - Assert.assertEquals("Please specify email", updateProfilePage.getError()); + Assert.assertEquals("Please specify email.", updateProfilePage.getError()); events.assertEmpty(); } @@ -159,7 +159,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - Assert.assertEquals("Email already exists", updateProfilePage.getError()); + Assert.assertEquals("Email already exists.", updateProfilePage.getError()); events.assertEmpty(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java index b1ae4cbe70..6579d4e2a5 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -286,7 +286,7 @@ public abstract class AbstractIdentityProviderTest { assertNotNull(element); - assertEquals("Email already exists", element.getText()); + assertEquals("Email already exists.", element.getText()); this.updateProfilePage.assertCurrent(); this.updateProfilePage.update("Test", "User", "test-user@redhat.com"); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java index 611c6bd10e..5f484dc8b2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java @@ -236,12 +236,12 @@ public class FederationProvidersIntegrationTest { // check existing username registerPage.register("firstName", "lastName", "email@mail.cz", "existing", "Password1", "Password1"); registerPage.assertCurrent(); - Assert.assertEquals("Username already exists", registerPage.getError()); + Assert.assertEquals("Username already exists.", registerPage.getError()); // Check existing email registerPage.register("firstName", "lastName", "existing@email.org", "nonExisting", "Password1", "Password1"); registerPage.assertCurrent(); - Assert.assertEquals("Email already exists", registerPage.getError()); + Assert.assertEquals("Email already exists.", registerPage.getError()); } @Test diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java index 75a83f4f8a..8c5a2f8b22 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java @@ -162,7 +162,7 @@ public class LoginTest { loginPage.assertCurrent(); - Assert.assertEquals("Account is disabled, contact admin", loginPage.getError()); + Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent(); } finally { @@ -316,7 +316,7 @@ public class LoginTest { loginPage.login("login@test.com", "password"); loginPage.assertCurrent(); - Assert.assertEquals("Login timeout. Please login again", loginPage.getError()); + Assert.assertEquals("Login timeout. Please login again.", loginPage.getError()); events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java index 2d0657a438..b2ac90e04e 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java @@ -78,7 +78,7 @@ public class RegisterTest { registerPage.register("firstName", "lastName", "registerExistingUser@email", "test-user@localhost", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Username already exists", registerPage.getError()); + Assert.assertEquals("Username already exists.", registerPage.getError()); events.expectRegister("test-user@localhost", "registerExistingUser@email").user((String) null).error("username_in_use").assertEvent(); } @@ -92,7 +92,7 @@ public class RegisterTest { registerPage.register("firstName", "lastName", "registerUserInvalidPasswordConfirm@email", "registerUserInvalidPasswordConfirm", "password", "invalid"); registerPage.assertCurrent(); - Assert.assertEquals("Password confirmation doesn't match", registerPage.getError()); + Assert.assertEquals("Password confirmation doesn't match.", registerPage.getError()); events.expectRegister("registerUserInvalidPasswordConfirm", "registerUserInvalidPasswordConfirm@email").user((String) null).error("invalid_registration").assertEvent(); } @@ -157,7 +157,7 @@ public class RegisterTest { registerPage.register("firstName", "lastName", "registerUserMissingUsername@email", null, "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Please specify username", registerPage.getError()); + Assert.assertEquals("Please specify username.", registerPage.getError()); events.expectRegister(null, "registerUserMissingUsername@email").removeDetail("username").error("invalid_registration").assertEvent(); } @@ -170,12 +170,12 @@ public class RegisterTest { registerPage.register("firstName", "lastName", null, "registerUserMissingEmail", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Please specify email", registerPage.getError()); + Assert.assertEquals("Please specify email.", registerPage.getError()); events.expectRegister("registerUserMissingEmail", null).removeDetail("email").error("invalid_registration").assertEvent(); registerPage.register("firstName", "lastName", "registerUserInvalidEmailemail", "registerUserInvalidEmail", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Invalid email address", registerPage.getError()); + Assert.assertEquals("Invalid email address.", registerPage.getError()); events.expectRegister("registerUserInvalidEmail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent(); } @@ -205,7 +205,7 @@ public class RegisterTest { registerPage.registerWithEmailAsUsername("firstName", "lastName", "test-user@localhost", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Username already exists", registerPage.getError()); + Assert.assertEquals("Username already exists.", registerPage.getError()); events.expectRegister("test-user@localhost", "test-user@localhost").user((String) null).error("username_in_use").assertEvent(); } finally { @@ -224,12 +224,12 @@ public class RegisterTest { registerPage.registerWithEmailAsUsername("firstName", "lastName", null, "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Please specify email", registerPage.getError()); + Assert.assertEquals("Please specify email.", registerPage.getError()); events.expectRegister(null, null).removeDetail("username").removeDetail("email").error("invalid_registration").assertEvent(); registerPage.registerWithEmailAsUsername("firstName", "lastName", "registerUserInvalidEmailemail", "password", "password"); registerPage.assertCurrent(); - Assert.assertEquals("Invalid email address", registerPage.getError()); + Assert.assertEquals("Invalid email address.", registerPage.getError()); events.expectRegister("registerUserInvalidEmailemail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent(); } finally { configureRelamRegistrationEmailAsUsername(false); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java index 7582e2094f..a96b660a54 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java @@ -504,7 +504,7 @@ public class ResetPasswordTest { events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent(); assertTrue(infoPage.isCurrent()); - assertEquals("Password updated", infoPage.getInfo()); + assertEquals("Your password has been updated", infoPage.getInfo()); loginPage.open(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java index c75a030821..9aa884ffe8 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java @@ -108,7 +108,7 @@ public class OAuthRedirectUriTest { oauth.openLoginForm(); Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } finally { keycloakRule.update(new KeycloakRule.KeycloakSetup() { @Override @@ -133,7 +133,7 @@ public class OAuthRedirectUriTest { oauth.openLoginForm(); Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } finally { keycloakRule.update(new KeycloakRule.KeycloakSetup() { @Override @@ -158,7 +158,7 @@ public class OAuthRedirectUriTest { oauth.openLoginForm(); Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } finally { keycloakRule.update(new KeycloakRule.KeycloakSetup() { @Override @@ -184,7 +184,7 @@ public class OAuthRedirectUriTest { oauth.openLoginForm(); Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } @Test @@ -244,7 +244,7 @@ public class OAuthRedirectUriTest { Assert.assertTrue(loginPage.isCurrent()); } else { Assert.assertTrue(errorPage.isCurrent()); - Assert.assertEquals("Invalid redirect_uri", errorPage.getError()); + Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError()); } if (expectValid) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java index 1766a8766c..fc83347637 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java @@ -56,7 +56,7 @@ public class LoginPage extends AbstractPage { @FindBy(linkText = "Register") private WebElement registerLink; - @FindBy(linkText = "Password") + @FindBy(linkText = "Forgot Password?") private WebElement resetPasswordLink; @FindBy(linkText = "Username") From e2b02d414fde51f0047fd033785679d1c121c560 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 13 Mar 2015 12:10:59 +0100 Subject: [PATCH 7/7] Change adapters to use auth and token endpoints --- .../org/keycloak/AbstractOAuthClient.java | 19 ++---- .../constants/ServiceUrlConstants.java | 6 +- .../org/keycloak/example/AdminClient.java | 2 +- .../example/oauth/DatabaseClient.java | 2 +- .../example/oauth/ProductDatabaseClient.java | 2 +- .../main/resources/theme/admin/base/index.ftl | 2 +- .../adapters/AdapterDeploymentContext.java | 9 +-- .../org/keycloak/adapters/AdapterUtils.java | 2 +- .../BasicAuthRequestAuthenticator.java | 3 +- .../keycloak/adapters/KeycloakDeployment.java | 16 ++--- .../adapters/KeycloakDeploymentBuilder.java | 2 +- .../adapters/OAuthRequestAuthenticator.java | 1 + .../org/keycloak/adapters/ServerRequest.java | 16 ++--- .../jaas/DirectAccessGrantsLoginModule.java | 3 +- .../KeycloakDeploymentBuilderTest.java | 4 +- .../adapters/installed/KeycloakInstalled.java | 2 + .../org/keycloak/jaxrs/JaxrsOAuthClient.java | 2 +- integration/js/src/main/resources/keycloak.js | 8 +-- .../keycloak/servlet/ServletOAuthClient.java | 4 +- .../servlet/ServletOAuthClientBuilder.java | 11 +--- .../oidc/OIDCLoginProtocolService.java | 34 ++++------- .../oidc/endpoints/AuthorizationEndpoint.java | 3 +- .../oidc/endpoints/TokenEndpoint.java | 60 +++++++------------ .../services/resources/AccountService.java | 3 +- .../services/resources/JsResource.java | 24 ++++++++ .../services/resources/ThemeResource.java | 7 ++- .../resources/admin/AdminConsole.java | 2 + .../resources/flows/OAuthRedirect.java | 3 +- .../keycloak/testutils/KeycloakServer.java | 15 +++++ .../org/keycloak/testsuite/OAuthClient.java | 32 ++++------ .../adapter/AdapterTestStrategy.java | 7 ++- .../adapter/CookieTokenStoreAdapterTest.java | 2 +- .../testsuite/adapter/MultiTenancyTest.java | 2 +- .../adapter/RelativeUriAdapterTest.java | 2 +- .../broker/AbstractIdentityProviderTest.java | 18 +++--- .../broker/IdentityProviderHintTest.java | 2 +- .../testsuite/oauth/AccessTokenTest.java | 20 ++++--- .../testsuite/oauth/RefreshTokenTest.java | 10 ++-- ...urceOwnerPasswordCredentialsGrantTest.java | 6 +- .../keycloak/testsuite/oidc/UserInfoTest.java | 6 +- .../testsuite/perf/AccessTokenPerfTest.java | 4 +- .../performance/web/OAuthClient.java | 8 +-- .../org/keycloak/testsuite/ProxyTest.java | 2 +- 43 files changed, 192 insertions(+), 196 deletions(-) diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java index 00fa0ed607..3b29907e69 100755 --- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java +++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java @@ -18,8 +18,7 @@ public class AbstractOAuthClient { protected String clientId; protected Map credentials; protected String authUrl; - protected String codeUrl; - protected String refreshUrl; + protected String tokenUrl; protected RelativeUrlsUsed relativeUrlsUsed; protected String scope; protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE; @@ -54,20 +53,12 @@ public class AbstractOAuthClient { this.authUrl = authUrl; } - public String getCodeUrl() { - return codeUrl; + public String getTokenUrl() { + return tokenUrl; } - public void setCodeUrl(String codeUrl) { - this.codeUrl = codeUrl; - } - - public String getRefreshUrl() { - return refreshUrl; - } - - public void setRefreshUrl(String refreshUrl) { - this.refreshUrl = refreshUrl; + public void setTokenUrl(String tokenUrl) { + this.tokenUrl = tokenUrl; } public String getScope() { diff --git a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java index 939906adeb..ae0b686b53 100755 --- a/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java +++ b/core/src/main/java/org/keycloak/constants/ServiceUrlConstants.java @@ -6,11 +6,9 @@ package org.keycloak.constants; */ public interface ServiceUrlConstants { - public static final String TOKEN_SERVICE_LOGIN_PATH = "/realms/{realm-name}/protocol/openid-connect/login"; - public static final String TOKEN_SERVICE_ACCESS_CODE_PATH = "/realms/{realm-name}/protocol/openid-connect/access/codes"; - public static final String TOKEN_SERVICE_REFRESH_PATH = "/realms/{realm-name}/protocol/openid-connect/refresh"; + public static final String AUTH_PATH = "/realms/{realm-name}/protocol/openid-connect/auth"; + public static final String TOKEN_PATH = "/realms/{realm-name}/protocol/openid-connect/token"; public static final String TOKEN_SERVICE_LOGOUT_PATH = "/realms/{realm-name}/protocol/openid-connect/logout"; - public static final String TOKEN_SERVICE_DIRECT_GRANT_PATH = "/realms/{realm-name}/protocol/openid-connect/grants/access"; public static final String ACCOUNT_SERVICE_PATH = "/realms/{realm-name}/account"; public static final String REALM_INFO_PATH = "/realms/{realm-name}"; public static final String CLIENTS_MANAGEMENT_REGISTER_NODE_PATH = "/realms/{realm-name}/clients-managements/register-node"; diff --git a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java index 9f7ec379d0..8f6c951b69 100755 --- a/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java +++ b/examples/demo-template/admin-access-app/src/main/java/org/keycloak/example/AdminClient.java @@ -76,7 +76,7 @@ public class AdminClient { try { HttpPost post = new HttpPost(KeycloakUriBuilder.fromUri(getBaseUrl(request) + "/auth") - .path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build("demo")); + .path(ServiceUrlConstants.TOKEN_PATH).build("demo")); List formparams = new ArrayList (); formparams.add(new BasicNameValuePair("username", "admin")); formparams.add(new BasicNameValuePair("password", "password")); diff --git a/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java b/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java index e642605f3c..01ede2eec9 100755 --- a/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java +++ b/examples/demo-template/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java @@ -109,7 +109,7 @@ public class DatabaseClient { return UriUtils.getOrigin(request.getRequestURL().toString()); case BROWSER_ONLY: // Resolve baseURI from the codeURL (This is already non-relative and based on our hostname) - return UriUtils.getOrigin(oauthClient.getCodeUrl()); + return UriUtils.getOrigin(oauthClient.getTokenUrl()); case NEVER: return ""; default: diff --git a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java index 24639085ae..3f69ef91f1 100755 --- a/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java +++ b/examples/demo-template/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java @@ -104,7 +104,7 @@ public class ProductDatabaseClient { return UriUtils.getOrigin(request.getRequestURL().toString()); case BROWSER_ONLY: // Resolve baseURI from the codeURL (This is already non-relative and based on our hostname) - return UriUtils.getOrigin(oAuthClient.getCodeUrl()); + return UriUtils.getOrigin(oAuthClient.getTokenUrl()); case NEVER: return ""; default: diff --git a/forms/common-themes/src/main/resources/theme/admin/base/index.ftl b/forms/common-themes/src/main/resources/theme/admin/base/index.ftl index e4a8a19ea4..161acdad73 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/index.ftl +++ b/forms/common-themes/src/main/resources/theme/admin/base/index.ftl @@ -26,7 +26,7 @@ - + diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java index d8a2141f69..50a13f962a 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterDeploymentContext.java @@ -168,13 +168,8 @@ public class AdapterDeploymentContext { } @Override - public String getCodeUrl() { - return (this.codeUrl != null) ? this.codeUrl : delegate.getCodeUrl(); - } - - @Override - public String getRefreshUrl() { - return (this.refreshUrl != null) ? this.refreshUrl : delegate.getRefreshUrl(); + public String getTokenUrl() { + return (this.tokenUrl != null) ? this.tokenUrl : delegate.getTokenUrl(); } @Override diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java index 28b3d7dd1d..2420721bf5 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/AdapterUtils.java @@ -38,7 +38,7 @@ public class AdapterUtils { return UriUtils.getOrigin(browserRequestURL); case BROWSER_ONLY: // Resolve baseURI from the codeURL (This is already non-relative and based on our hostname) - return UriUtils.getOrigin(deployment.getCodeUrl()); + return UriUtils.getOrigin(deployment.getTokenUrl()); default: return ""; } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java index 55dc847bf7..51ddfd5efd 100644 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/BasicAuthRequestAuthenticator.java @@ -69,8 +69,9 @@ public class BasicAuthRequestAuthenticator extends BearerTokenRequestAuthenticat try { HttpPost post = new HttpPost( KeycloakUriBuilder.fromUri(deployment.getAuthServerBaseUrl()) - .path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build(deployment.getRealm())); + .path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm())); java.util.List formparams = new java.util.ArrayList (); + formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)); formparams.add(new BasicNameValuePair("username", username)); formparams.add(new BasicNameValuePair("password", password)); diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java index e9dfaf608f..f792046bc4 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeployment.java @@ -28,8 +28,7 @@ public class KeycloakDeployment { protected String authServerBaseUrl; protected String realmInfoUrl; protected KeycloakUriBuilder authUrl; - protected String codeUrl; - protected String refreshUrl; + protected String tokenUrl; protected KeycloakUriBuilder logoutUrl; protected String accountUrl; protected String registerNodeUrl; @@ -131,7 +130,7 @@ public class KeycloakDeployment { log.debug("resolveBrowserUrls"); } - String login = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH).build(getRealm()).toString(); + String login = authUrlBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(getRealm()).toString(); authUrl = KeycloakUriBuilder.fromUri(login); } @@ -143,11 +142,10 @@ public class KeycloakDeployment { log.debug("resolveNonBrowserUrls"); } - refreshUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(getRealm()).toString(); + tokenUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_PATH).build(getRealm()).toString(); logoutUrl = KeycloakUriBuilder.fromUri(authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGOUT_PATH).build(getRealm()).toString()); accountUrl = authUrlBuilder.clone().path(ServiceUrlConstants.ACCOUNT_SERVICE_PATH).build(getRealm()).toString(); realmInfoUrl = authUrlBuilder.clone().path(ServiceUrlConstants.REALM_INFO_PATH).build(getRealm()).toString(); - codeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(getRealm()).toString(); registerNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_REGISTER_NODE_PATH).build(getRealm()).toString(); unregisterNodeUrl = authUrlBuilder.clone().path(ServiceUrlConstants.CLIENTS_MANAGEMENT_UNREGISTER_NODE_PATH).build(getRealm()).toString(); } @@ -164,12 +162,8 @@ public class KeycloakDeployment { return authUrl; } - public String getCodeUrl() { - return codeUrl; - } - - public String getRefreshUrl() { - return refreshUrl; + public String getTokenUrl() { + return tokenUrl; } public KeycloakUriBuilder getLogoutUrl() { diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java index 9c2129ccf1..be358aa2b0 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/KeycloakDeploymentBuilder.java @@ -85,7 +85,7 @@ public class KeycloakDeploymentBuilder { } deployment.setAuthServerBaseUrl(adapterConfig); - log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", codeUrl: " + deployment.getCodeUrl() + ", relativeUrls: " + deployment.getRelativeUrls()); + log.debug("Use authServerUrl: " + deployment.getAuthServerBaseUrl() + ", tokenUrl: " + deployment.getTokenUrl() + ", relativeUrls: " + deployment.getRelativeUrls()); return deployment; } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java index c49fa2cf45..90a22047e6 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/OAuthRequestAuthenticator.java @@ -136,6 +136,7 @@ public class OAuthRequestAuthenticator { url = UriUtils.stripQueryParam(url, K_IDP_HINT); KeycloakUriBuilder redirectUriBuilder = deployment.getAuthUrl().clone() + .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.REDIRECT_URI, url) .queryParam(OAuth2Constants.STATE, state) diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java index eebac4f91a..27a2949520 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/ServerRequest.java @@ -87,15 +87,15 @@ public class ServerRequest { } public static AccessTokenResponse invokeAccessCodeToToken(KeycloakDeployment deployment, String code, String redirectUri, String sessionId) throws HttpFailure, IOException { - String codeUrl = deployment.getCodeUrl(); + String tokenUrl = deployment.getTokenUrl(); String client_id = deployment.getResourceName(); Map credentials = deployment.getResourceCredentials(); HttpClient client = deployment.getClient(); - return invokeAccessCodeToToken(client, deployment.isPublicClient(), code, codeUrl, redirectUri, client_id, credentials, sessionId); + return invokeAccessCodeToToken(client, deployment.isPublicClient(), code, tokenUrl, redirectUri, client_id, credentials, sessionId); } - public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String codeUrl, String redirectUri, String client_id, Map credentials, String sessionId) throws IOException, HttpFailure { + public static AccessTokenResponse invokeAccessCodeToToken(HttpClient client, boolean publicClient, String code, String tokenUrl, String redirectUri, String client_id, Map credentials, String sessionId) throws IOException, HttpFailure { List formparams = new ArrayList(); redirectUri = stripOauthParametersFromRedirect(redirectUri); formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, "authorization_code")); @@ -106,7 +106,7 @@ public class ServerRequest { formparams.add(new BasicNameValuePair(AdapterConstants.APPLICATION_SESSION_HOST, HostUtils.getHostName())); } HttpResponse response = null; - HttpPost post = new HttpPost(codeUrl); + HttpPost post = new HttpPost(tokenUrl); if (!publicClient) { String clientSecret = credentials.get(CredentialRepresentation.SECRET); if (clientSecret != null) { @@ -152,15 +152,15 @@ public class ServerRequest { } public static AccessTokenResponse invokeRefresh(KeycloakDeployment deployment, String refreshToken) throws IOException, HttpFailure { - String refreshUrl = deployment.getRefreshUrl(); + String tokenUrl = deployment.getTokenUrl(); String client_id = deployment.getResourceName(); Map credentials = deployment.getResourceCredentials(); HttpClient client = deployment.getClient(); - return invokeRefresh(client, deployment.isPublicClient(), refreshToken, refreshUrl, client_id, credentials); + return invokeRefresh(client, deployment.isPublicClient(), refreshToken, tokenUrl, client_id, credentials); } - public static AccessTokenResponse invokeRefresh(HttpClient client, boolean publicClient, String refreshToken, String refreshUrl, String client_id, Map credentials) throws IOException, HttpFailure { + public static AccessTokenResponse invokeRefresh(HttpClient client, boolean publicClient, String refreshToken, String tokenUrl, String client_id, Map credentials) throws IOException, HttpFailure { List formparams = new ArrayList(); for (Map.Entry entry : credentials.entrySet()) { formparams.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); @@ -168,7 +168,7 @@ public class ServerRequest { formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); formparams.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); HttpResponse response = null; - HttpPost post = new HttpPost(refreshUrl); + HttpPost post = new HttpPost(tokenUrl); if (!publicClient) { String clientSecret = credentials.get(CredentialRepresentation.SECRET); if (clientSecret != null) { diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java index da76aae422..a11e1ee4a3 100644 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/jaas/DirectAccessGrantsLoginModule.java @@ -64,10 +64,11 @@ public class DirectAccessGrantsLoginModule extends AbstractKeycloakLoginModule { protected Auth directGrantAuth(String username, String password) throws IOException, VerificationException { String authServerBaseUrl = deployment.getAuthServerBaseUrl(); - URI directGrantUri = KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.TOKEN_SERVICE_DIRECT_GRANT_PATH).build(deployment.getRealm()); + URI directGrantUri = KeycloakUriBuilder.fromUri(authServerBaseUrl).path(ServiceUrlConstants.TOKEN_PATH).build(deployment.getRealm()); HttpPost post = new HttpPost(directGrantUri); List formparams = new ArrayList(); + formparams.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)); formparams.add(new BasicNameValuePair("username", username)); formparams.add(new BasicNameValuePair("password", password)); diff --git a/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java b/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java index 40390f9968..a40b83ec3d 100644 --- a/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java +++ b/integration/adapter-core/src/test/java/org/keycloak/adapters/KeycloakDeploymentBuilderTest.java @@ -20,7 +20,7 @@ public class KeycloakDeploymentBuilderTest { assertEquals("demo", deployment.getRealm()); assertEquals("customer-portal", deployment.getResourceName()); assertEquals(PemUtils.decodePublicKey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"), deployment.getRealmKey()); - assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/login", deployment.getAuthUrl().build().toString()); + assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/auth", deployment.getAuthUrl().build().toString()); assertEquals(SslRequired.EXTERNAL, deployment.getSslRequired()); assertTrue(deployment.isUseResourceRoleMappings()); assertTrue(deployment.isCors()); @@ -33,7 +33,7 @@ public class KeycloakDeploymentBuilderTest { assertTrue(deployment.isExposeToken()); assertEquals("234234-234234-234234", deployment.getResourceCredentials().get("secret")); assertEquals(20, ((ThreadSafeClientConnManager) deployment.getClient().getConnectionManager()).getMaxTotal()); - assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/refresh", deployment.getRefreshUrl()); + assertEquals("https://localhost:8443/auth/realms/demo/protocol/openid-connect/token", deployment.getTokenUrl()); assertTrue(deployment.isAlwaysRefreshToken()); assertTrue(deployment.isRegisterNodeAtStartup()); assertEquals(1000, deployment.getRegisterNodePeriod()); diff --git a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java index d3ed5367cb..e69f6c82db 100644 --- a/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java +++ b/integration/installed/src/main/java/org/keycloak/adapters/installed/KeycloakInstalled.java @@ -97,6 +97,7 @@ public class KeycloakInstalled { String state = UUID.randomUUID().toString(); String authUrl = deployment.getAuthUrl().clone() + .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) .queryParam(OAuth2Constants.STATE, state) @@ -153,6 +154,7 @@ public class KeycloakInstalled { String redirectUri = "urn:ietf:wg:oauth:2.0:oob"; String authUrl = deployment.getAuthUrl().clone() + .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE) .queryParam(OAuth2Constants.CLIENT_ID, deployment.getResourceName()) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) .build().toString(); diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java index 6944782e2d..04ef9a2246 100755 --- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java +++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java @@ -53,7 +53,7 @@ public class JaxrsOAuthClient extends AbstractOAuthClient { for (Map.Entry entry : credentials.entrySet()) { codeForm.param(entry.getKey(), entry.getValue()); } - Response res = client.target(codeUrl).request().post(Entity.form(codeForm)); + Response res = client.target(tokenUrl).request().post(Entity.form(codeForm)); try { if (res.getStatus() == 400) { throw new BadRequestException(); diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js index 1b0813b0ed..34118c251f 100755 --- a/integration/js/src/main/resources/keycloak.js +++ b/integration/js/src/main/resources/keycloak.js @@ -140,7 +140,7 @@ sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) }); - var action = 'login'; + var action = 'auth'; if (options && options.action == 'register') { action = 'registrations'; } @@ -284,7 +284,7 @@ promise.setSuccess(false); } else { var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; - var url = getRealmUrl() + '/protocol/openid-connect/refresh'; + var url = getRealmUrl() + '/protocol/openid-connect/token'; refreshQueue.push(promise); @@ -358,8 +358,8 @@ var prompt = oauth.prompt; if (code) { - var params = 'code=' + code; - var url = getRealmUrl() + '/protocol/openid-connect/access/codes'; + var params = 'code=' + code + '&grant_type=authorization_code'; + var url = getRealmUrl() + '/protocol/openid-connect/token'; var req = new XMLHttpRequest(); req.open('POST', url, true); diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java index 2420c5308a..390721dadb 100755 --- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java +++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java @@ -42,7 +42,7 @@ public class ServletOAuthClient extends AbstractOAuthClient { private AccessTokenResponse resolveBearerToken(HttpServletRequest request, String redirectUri, String code) throws IOException, ServerRequest.HttpFailure { // Don't send sessionId in oauth clients for now - return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, codeUrl, false), redirectUri, clientId, credentials, null); + return ServerRequest.invokeAccessCodeToToken(client, publicClient, code, getUrl(request, tokenUrl, false), redirectUri, clientId, credentials, null); } /** @@ -148,7 +148,7 @@ public class ServletOAuthClient extends AbstractOAuthClient { } public AccessTokenResponse refreshToken(HttpServletRequest request, String refreshToken) throws IOException, ServerRequest.HttpFailure { - return ServerRequest.invokeRefresh(client, publicClient, refreshToken, getUrl(request, refreshUrl, false), clientId, credentials); + return ServerRequest.invokeRefresh(client, publicClient, refreshToken, getUrl(request, tokenUrl, false), clientId, credentials); } public static IDToken extractIdToken(String idToken) { diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java index 5620636aeb..d642e5c78b 100755 --- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java +++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientBuilder.java @@ -54,25 +54,20 @@ public class ServletOAuthClientBuilder { RelativeUrlsUsed useRelative = relativeUrls(serverBuilder, adapterConfig); oauthClient.setRelativeUrlsUsed(useRelative); - String authUrl = serverBuilder.clone().path(ServiceUrlConstants.TOKEN_SERVICE_LOGIN_PATH).build(adapterConfig.getRealm()).toString(); + String authUrl = serverBuilder.clone().path(ServiceUrlConstants.AUTH_PATH).build(adapterConfig.getRealm()).toString(); KeycloakUriBuilder tokenUrlBuilder; - KeycloakUriBuilder refreshUrlBuilder; if (useRelative == RelativeUrlsUsed.BROWSER_ONLY) { // Use absolute URI for refreshToken and codeToToken requests KeycloakUriBuilder nonBrowsersServerBuilder = KeycloakUriBuilder.fromUri(adapterConfig.getAuthServerUrlForBackendRequests()); tokenUrlBuilder = nonBrowsersServerBuilder.clone(); - refreshUrlBuilder = nonBrowsersServerBuilder.clone(); } else { tokenUrlBuilder = serverBuilder.clone(); - refreshUrlBuilder = serverBuilder.clone(); } - String tokenUrl = tokenUrlBuilder.path(ServiceUrlConstants.TOKEN_SERVICE_ACCESS_CODE_PATH).build(adapterConfig.getRealm()).toString(); - String refreshUrl = refreshUrlBuilder.path(ServiceUrlConstants.TOKEN_SERVICE_REFRESH_PATH).build(adapterConfig.getRealm()).toString(); + String tokenUrl = tokenUrlBuilder.path(ServiceUrlConstants.TOKEN_PATH).build(adapterConfig.getRealm()).toString(); oauthClient.setAuthUrl(authUrl); - oauthClient.setCodeUrl(tokenUrl); - oauthClient.setRefreshUrl(refreshUrl); + oauthClient.setTokenUrl(tokenUrl); } private static RelativeUrlsUsed relativeUrls(KeycloakUriBuilder serverBuilder, AdapterConfig adapterConfig) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java index fd4b3b154d..b4b0de573d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java @@ -86,9 +86,19 @@ public class OIDCLoginProtocolService { return baseUriBuilder.path(RealmsResource.class).path("{realm}/protocol/" + OIDCLoginProtocol.LOGIN_PROTOCOL); } - public static UriBuilder accessCodeToTokenUrl(UriBuilder baseUriBuilder) { + public static UriBuilder authUrl(UriInfo uriInfo) { + UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); + return authUrl(baseUriBuilder); + } + + public static UriBuilder authUrl(UriBuilder baseUriBuilder) { UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "accessCodeToToken"); + return uriBuilder.path(OIDCLoginProtocolService.class, "auth"); + } + + public static UriBuilder tokenUrl(UriBuilder baseUriBuilder) { + UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); + return uriBuilder.path(OIDCLoginProtocolService.class, "token"); } public static UriBuilder validateAccessTokenUrl(UriBuilder baseUriBuilder) { @@ -96,21 +106,6 @@ public class OIDCLoginProtocolService { return uriBuilder.path(OIDCLoginProtocolService.class, "validateAccessToken"); } - public static UriBuilder grantAccessTokenUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "grantAccessToken"); - } - - public static UriBuilder loginPageUrl(UriInfo uriInfo) { - UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); - return loginPageUrl(baseUriBuilder); - } - - public static UriBuilder loginPageUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "loginPage"); - } - public static UriBuilder logoutUrl(UriInfo uriInfo) { UriBuilder baseUriBuilder = uriInfo.getBaseUriBuilder(); return logoutUrl(baseUriBuilder); @@ -121,11 +116,6 @@ public class OIDCLoginProtocolService { return uriBuilder.path(OIDCLoginProtocolService.class, "logout"); } - public static UriBuilder refreshUrl(UriBuilder baseUriBuilder) { - UriBuilder uriBuilder = tokenServiceBaseUrl(baseUriBuilder); - return uriBuilder.path(OIDCLoginProtocolService.class, "refreshAccessToken"); - } - /** * Authorization endpoint */ diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index 708fb2119b..bebf70341c 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -106,8 +106,7 @@ public class AuthorizationEndpoint { * @deprecated */ public AuthorizationEndpoint legacy(String legacyResponseType) { - // TODO Change to warn once adapters has been updated - logger.debugv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri()); + logger.warnv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri()); this.legacyResponseType = legacyResponseType; return this; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index 730e9be9a6..d8f9db2815 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -47,6 +47,8 @@ import java.util.Map; public class TokenEndpoint { private static final Logger logger = Logger.getLogger(TokenEndpoint.class); + private MultivaluedMap formParams; + private ClientModel client; private enum Action { AUTHORIZATION_CODE, REFRESH_TOKEN, PASSWORD @@ -74,10 +76,7 @@ public class TokenEndpoint { private Action action; - private String clientId; private String grantType; - private String code; - private String redirectUri; private String legacyGrantType; @@ -89,15 +88,14 @@ public class TokenEndpoint { } @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response build(final MultivaluedMap formData) { + public Response build() { switch (action) { case AUTHORIZATION_CODE: - return buildAuthorizationCodeAccessTokenResponse(formData); + return buildAuthorizationCodeAccessTokenResponse(); case REFRESH_TOKEN: - return buildRefreshToken(formData); + return buildRefreshToken(); case PASSWORD: - return buildResourceOwnerPasswordCredentialsGrant(formData); + return buildResourceOwnerPasswordCredentialsGrant(); } throw new RuntimeException("Unknown action " + action); @@ -115,23 +113,19 @@ public class TokenEndpoint { * @deprecated */ public TokenEndpoint legacy(String legacyGrantType) { - // TODO Change to warn once adapters has been updated - logger.debugv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri()); + logger.warnv("Invoking deprecated endpoint {0}", uriInfo.getRequestUri()); this.legacyGrantType = legacyGrantType; return this; } public TokenEndpoint init() { - MultivaluedMap params = uriInfo.getQueryParameters(); - - clientId = params.getFirst(OIDCLoginProtocol.CLIENT_ID_PARAM); - grantType = params.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM); - code = params.getFirst(OIDCLoginProtocol.CODE_PARAM); - redirectUri = params.getFirst(OIDCLoginProtocol.REDIRECT_URI_PARAM); + formParams = request.getDecodedFormParameters(); + grantType = formParams.getFirst(OIDCLoginProtocol.GRANT_TYPE_PARAM); checkSsl(); checkRealm(); checkGrantType(); + checkClient(); return this; } @@ -148,15 +142,13 @@ public class TokenEndpoint { } } - private ClientModel authorizeClient(final MultivaluedMap formData) { + private void checkClient() { String authorizationHeader = headers.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); - ClientModel client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formData, event, realm); + client = AuthorizeClientUtil.authorizeClient(authorizationHeader, formParams, event, realm); if ((client instanceof ApplicationModel) && ((ApplicationModel) client).isBearerOnly()) { throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST); } - - return client; } private void checkGrantType() { @@ -182,8 +174,8 @@ public class TokenEndpoint { } } - public Response buildAuthorizationCodeAccessTokenResponse(final MultivaluedMap formData) { - String code = formData.getFirst(OAuth2Constants.CODE); + public Response buildAuthorizationCodeAccessTokenResponse() { + String code = formParams.getFirst(OAuth2Constants.CODE); if (code == null) { event.error(Errors.INVALID_CODE); throw new ErrorResponseException("invalid_request", "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST); @@ -214,10 +206,8 @@ public class TokenEndpoint { event.user(userSession.getUser()); event.session(userSession.getId()); - ClientModel client = authorizeClient(formData); - String redirectUri = clientSession.getNote(OIDCLoginProtocol.REDIRECT_URI_PARAM); - if (redirectUri != null && !redirectUri.equals(formData.getFirst(OAuth2Constants.REDIRECT_URI))) { + if (redirectUri != null && !redirectUri.equals(formParams.getFirst(OAuth2Constants.REDIRECT_URI))) { event.error(Errors.INVALID_CODE); throw new ErrorResponseException("invalid_grant", "Incorrect redirect_uri", Response.Status.BAD_REQUEST); } @@ -243,9 +233,9 @@ public class TokenEndpoint { throw new ErrorResponseException("invalid_grant", "Session not active", Response.Status.BAD_REQUEST); } - String adapterSessionId = formData.getFirst(AdapterConstants.APPLICATION_SESSION_STATE); + String adapterSessionId = formParams.getFirst(AdapterConstants.APPLICATION_SESSION_STATE); if (adapterSessionId != null) { - String adapterSessionHost = formData.getFirst(AdapterConstants.APPLICATION_SESSION_HOST); + String adapterSessionHost = formParams.getFirst(AdapterConstants.APPLICATION_SESSION_HOST); logger.debugf("Adapter Session '%s' saved in ClientSession for client '%s'. Host is '%s'", adapterSessionId, client.getClientId(), adapterSessionHost); event.detail(AdapterConstants.APPLICATION_SESSION_STATE, adapterSessionId); @@ -266,10 +256,8 @@ public class TokenEndpoint { return Cors.add(request, Response.ok(res).type(MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); } - public Response buildRefreshToken(final MultivaluedMap formData) { - ClientModel client = authorizeClient(formData); - - String refreshToken = formData.getFirst(OAuth2Constants.REFRESH_TOKEN); + public Response buildRefreshToken() { + String refreshToken = formParams.getFirst(OAuth2Constants.REFRESH_TOKEN); if (refreshToken == null) { throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No refresh token", Response.Status.BAD_REQUEST); } @@ -287,14 +275,14 @@ public class TokenEndpoint { return Cors.add(request, Response.ok(res, MediaType.APPLICATION_JSON_TYPE)).auth().allowedOrigins(client).allowedMethods("POST").exposedHeaders(Cors.ACCESS_CONTROL_ALLOW_METHODS).build(); } - public Response buildResourceOwnerPasswordCredentialsGrant(final MultivaluedMap formData) { + public Response buildResourceOwnerPasswordCredentialsGrant() { if (!realm.isPasswordCredentialGrantAllowed()) { throw new ErrorResponseException("not_enabled", "Direct Grant REST API not enabled", Response.Status.FORBIDDEN); } event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, "token"); - String username = formData.getFirst(AuthenticationManager.FORM_USERNAME); + String username = formParams.getFirst(AuthenticationManager.FORM_USERNAME); if (username == null) { event.error(Errors.USERNAME_MISSING); throw new ErrorResponseException("invalid_request", "Missing parameter: username", Response.Status.UNAUTHORIZED); @@ -304,9 +292,7 @@ public class TokenEndpoint { UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username); if (user != null) event.user(user); - ClientModel client = authorizeClient(formData); - - AuthenticationManager.AuthenticationStatus authenticationStatus = authManager.authenticateForm(session, clientConnection, realm, formData); + AuthenticationManager.AuthenticationStatus authenticationStatus = authManager.authenticateForm(session, clientConnection, realm, formParams); Map err; switch (authenticationStatus) { @@ -324,7 +310,7 @@ public class TokenEndpoint { throw new ErrorResponseException("invalid_grant", "Invalid user credentials", Response.Status.UNAUTHORIZED); } - String scope = formData.getFirst(OAuth2Constants.SCOPE); + String scope = formParams.getFirst(OAuth2Constants.SCOPE); UserSessionProvider sessions = session.sessions(); diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 1efc33a032..fae89525c7 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -25,6 +25,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.spi.BadRequestException; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.ClientConnection; +import org.keycloak.OAuth2Constants; import org.keycloak.account.AccountPages; import org.keycloak.account.AccountProvider; import org.keycloak.events.Details; @@ -763,7 +764,7 @@ public class AccountService { private Response login(String path) { OAuthRedirect oauth = new OAuthRedirect(); - String authUrl = OIDCLoginProtocolService.loginPageUrl(uriInfo).build(realm.getName()).toString(); + String authUrl = OIDCLoginProtocolService.authUrl(uriInfo).build(realm.getName()).toString(); oauth.setAuthUrl(authUrl); oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APP); diff --git a/services/src/main/java/org/keycloak/services/resources/JsResource.java b/services/src/main/java/org/keycloak/services/resources/JsResource.java index 6c59c78c04..1b0128805a 100755 --- a/services/src/main/java/org/keycloak/services/resources/JsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/JsResource.java @@ -1,9 +1,11 @@ package org.keycloak.services.resources; import org.keycloak.Config; +import org.keycloak.Version; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.Response; @@ -38,6 +40,17 @@ public class JsResource { } } + @GET + @Path("/{version}/keycloak.js") + @Produces("text/javascript") + public Response getJsWithVersion(@PathParam("version") String version) { + if (!version.equals(Version.RESOURCES_VERSION)) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return getJs(); + } + @GET @Path("/keycloak.min.js") @Produces("text/javascript") @@ -54,4 +67,15 @@ public class JsResource { } } + @GET + @Path("/{version}/keycloak.min.js") + @Produces("text/javascript") + public Response getMinJsWithVersion(@PathParam("version") String version) { + if (!version.equals(Version.RESOURCES_VERSION)) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + + return getMinJs(); + } + } diff --git a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java index 488774d275..5fe27a4117 100755 --- a/services/src/main/java/org/keycloak/services/resources/ThemeResource.java +++ b/services/src/main/java/org/keycloak/services/resources/ThemeResource.java @@ -2,6 +2,7 @@ package org.keycloak.services.resources; import org.jboss.logging.Logger; import org.keycloak.Config; +import org.keycloak.Version; import org.keycloak.freemarker.Theme; import org.keycloak.freemarker.ThemeProvider; import org.keycloak.models.KeycloakSession; @@ -38,7 +39,11 @@ public class ThemeResource { */ @GET @Path("/{version}/{themeType}/{themeName}/{path:.*}") - public Response getResource(@PathParam("themeType") String themType, @PathParam("themeName") String themeName, @PathParam("path") String path) { + public Response getResource(@PathParam("version") String version, @PathParam("themeType") String themType, @PathParam("themeName") String themeName, @PathParam("path") String path) { + if (!version.equals(Version.RESOURCES_VERSION)) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + try { ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme = themeProvider.getTheme(themeName, Theme.Type.valueOf(themType.toUpperCase())); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index ff6e0fcbfa..362f72a7f4 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -8,6 +8,7 @@ import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.NotFoundException; import org.keycloak.ClientConnection; import org.keycloak.Config; +import org.keycloak.Version; import org.keycloak.freemarker.BrowserSecurityHeaderSetup; import org.keycloak.freemarker.FreeMarkerException; import org.keycloak.freemarker.FreeMarkerUtil; @@ -290,6 +291,7 @@ public class AdminConsole { map.put("authUrl", authUrl); map.put("resourceUrl", Urls.themeRoot(baseUri) + "/admin/" + adminTheme); + map.put("resourceVersion", Version.RESOURCES_VERSION); ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); Theme theme = themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN); diff --git a/services/src/main/java/org/keycloak/services/resources/flows/OAuthRedirect.java b/services/src/main/java/org/keycloak/services/resources/flows/OAuthRedirect.java index 7d3eef017d..2ccb4a004a 100755 --- a/services/src/main/java/org/keycloak/services/resources/flows/OAuthRedirect.java +++ b/services/src/main/java/org/keycloak/services/resources/flows/OAuthRedirect.java @@ -34,7 +34,8 @@ public class OAuthRedirect extends AbstractOAuthClient { UriBuilder uriBuilder = UriBuilder.fromUri(authUrl) .queryParam(OAuth2Constants.CLIENT_ID, clientId) .queryParam(OAuth2Constants.REDIRECT_URI, redirectUri) - .queryParam(OAuth2Constants.STATE, state); + .queryParam(OAuth2Constants.STATE, state) + .queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE); if (scope != null) { uriBuilder.queryParam(OAuth2Constants.SCOPE, scope); } diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java index d5e1c375a6..73c78e0589 100755 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/KeycloakServer.java @@ -46,12 +46,27 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; /** * @author Stian Thorgersen */ public class KeycloakServer { + static { + try { + File f = new File(System.getProperty("user.home"), ".keycloak-test.properties"); + if (f.isFile()) { + Properties p = new Properties(); + p.load(new FileInputStream(f)); + System.getProperties().putAll(p); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + private static final Logger log = Logger.getLogger(KeycloakServer.class); private boolean sysout = false; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java index a32a70dc1d..9319661732 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java @@ -67,10 +67,6 @@ public class OAuthClient { private String realm = "test"; - private String responseType = OAuth2Constants.CODE; - - private String grantType = "authorization_code"; - private String clientId = "test-app"; private String redirectUri = "http://localhost:8081/app/auth"; @@ -113,9 +109,8 @@ public class OAuthClient { HttpPost post = new HttpPost(getAccessTokenUrl()); List parameters = new LinkedList(); - if (grantType != null) { - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType)); - } + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.AUTHORIZATION_CODE)); + if (code != null) { parameters.add(new BasicNameValuePair(OAuth2Constants.CODE, code)); } @@ -153,6 +148,7 @@ public class OAuthClient { post.setHeader("Authorization", authorization); List parameters = new LinkedList(); + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD)); parameters.add(new BasicNameValuePair("username", username)); parameters.add(new BasicNameValuePair("password", password)); @@ -199,9 +195,8 @@ public class OAuthClient { HttpPost post = new HttpPost(getRefreshTokenUrl()); List parameters = new LinkedList(); - if (grantType != null) { - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, grantType)); - } + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN)); + if (refreshToken != null) { parameters.add(new BasicNameValuePair(OAuth2Constants.REFRESH_TOKEN, refreshToken)); } @@ -290,10 +285,8 @@ public class OAuthClient { } public String getLoginFormUrl() { - UriBuilder b = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri(baseUrl)); - if (responseType != null) { - b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType); - } + UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(baseUrl)); + b.queryParam(OAuth2Constants.RESPONSE_TYPE, OAuth2Constants.CODE); if (clientId != null) { b.queryParam(OAuth2Constants.CLIENT_ID, clientId); } @@ -307,7 +300,7 @@ public class OAuthClient { } public String getAccessTokenUrl() { - UriBuilder b = OIDCLoginProtocolService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } @@ -323,12 +316,12 @@ public class OAuthClient { } public String getResourceOwnerPasswordCredentialGrantUrl() { - UriBuilder b = OIDCLoginProtocolService.grantAccessTokenUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } public String getRefreshTokenUrl() { - UriBuilder b = OIDCLoginProtocolService.refreshUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } @@ -351,11 +344,6 @@ public class OAuthClient { return this; } - public OAuthClient responseType(String responseType) { - this.responseType = responseType; - return this; - } - public OAuthClient state(String state) { this.state = state; return this; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index e1d83b32a3..cfc9534e7b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -92,7 +92,7 @@ public class AdapterTestStrategy extends ExternalResource { @WebResource protected InputPage inputPage; - protected String LOGIN_URL = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri(AUTH_SERVER_URL)).build("demo").toString(); + protected String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(AUTH_SERVER_URL)).build("demo").toString(); public AdapterTestStrategy(String AUTH_SERVER_URL, String APP_SERVER_BASE_URL, AbstractKeycloakRule keycloakRule) { this.AUTH_SERVER_URL = AUTH_SERVER_URL; @@ -420,11 +420,12 @@ public class AdapterTestStrategy extends ExternalResource { public void testBadUser() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(AUTH_SERVER_URL); - URI uri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("demo"); + URI uri = OIDCLoginProtocolService.tokenUrl(builder).build("demo"); WebTarget target = client.target(uri); String header = BasicAuthHelper.createHeader("customer-portal", "password"); Form form = new Form(); - form.param("username", "monkey@redhat.com") + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) + .param("username", "monkey@redhat.com") .param("password", "password"); Response response = target.request() .header(HttpHeaders.AUTHORIZATION, header) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java index 54980c6960..632202d542 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java @@ -31,7 +31,7 @@ import org.openqa.selenium.WebDriver; */ public class CookieTokenStoreAdapterTest { - public static final String LOGIN_URL = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); + public static final String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); @ClassRule public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule() { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java index 8d4b835d23..235d5f54c4 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/MultiTenancyTest.java @@ -126,7 +126,7 @@ public class MultiTenancyTest { } private void doTenantRequests(String tenant, boolean logout) { - String tenantLoginUrl = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build(tenant).toString(); + String tenantLoginUrl = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build(tenant).toString(); driver.navigate().to("http://localhost:8081/multi-tenant?realm="+tenant); System.out.println("Current url: " + driver.getCurrentUrl()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java index cce65bd49e..01a5861350 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java @@ -66,7 +66,7 @@ import java.util.Map; */ public class RelativeUriAdapterTest { - public static final String LOGIN_URL = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); + public static final String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); public static PublicKey realmPublicKey; @ClassRule public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule(){ diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java index 6579d4e2a5..5b66f7f1df 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -175,7 +175,7 @@ public abstract class AbstractIdentityProviderTest { driver.navigate().to("http://localhost:8081/test-app/logout"); driver.navigate().to("http://localhost:8081/test-app"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); } finally { getRealm().setRegistrationEmailAsUsername(false); @@ -215,7 +215,7 @@ public abstract class AbstractIdentityProviderTest { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); try { this.driver.findElement(By.className(getProviderId())); @@ -245,7 +245,7 @@ public abstract class AbstractIdentityProviderTest { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); try { this.driver.findElement(By.className(getProviderId())); @@ -267,7 +267,7 @@ public abstract class AbstractIdentityProviderTest { public void testUserAlreadyExistsWhenUpdatingProfile() { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); // choose the identity provider this.loginPage.clickSocial(getProviderId()); @@ -306,7 +306,7 @@ public abstract class AbstractIdentityProviderTest { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); // choose the identity provider this.loginPage.clickSocial(getProviderId()); @@ -374,7 +374,7 @@ public abstract class AbstractIdentityProviderTest { public void testIdentityProviderNotAllowed() { this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); driver.findElement(By.className("model-oidc-idp")); } @@ -438,7 +438,7 @@ public abstract class AbstractIdentityProviderTest { driver.navigate().to("http://localhost:8081/test-app/logout"); driver.navigate().to("http://localhost:8081/test-app"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); } @Test @@ -533,13 +533,13 @@ public abstract class AbstractIdentityProviderTest { driver.navigate().to("http://localhost:8081/test-app/logout"); driver.navigate().to("http://localhost:8081/test-app"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); } private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username) { driver.navigate().to("http://localhost:8081/test-app"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); // choose the identity provider this.loginPage.clickSocial(getProviderId()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java index b46617eac4..bac23e8c2b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/IdentityProviderHintTest.java @@ -74,7 +74,7 @@ public class IdentityProviderHintTest { public void testInvalidIdentityProviderHint() { this.driver.navigate().to("http://localhost:8081/test-app?k_idp_hint=invalid-idp-id"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/login")); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); assertEquals("Could not find an identity provider with the identifier [invalid-idp-id].", this.driver.findElement(By.className("instruction")).getText()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index 56538a738c..34342e04b9 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -149,7 +149,7 @@ public class AccessTokenTest { AccessTokenResponse response = oauth.doAccessTokenRequest(code, "invalid"); Assert.assertEquals(400, response.getStatusCode()); - AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, loginEvent.getSessionId()).error("invalid_client_credentials").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID); + AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, loginEvent.getSessionId()).error("invalid_client_credentials").clearDetails().user((String) null).session((String) null); expectedEvent.assertEvent(); } @@ -190,7 +190,7 @@ public class AccessTokenTest { assertNull(tokenResponse.getAccessToken()); assertNull(tokenResponse.getRefreshToken()); - events.expectCodeToToken(codeId, sessionId).removeDetail(Details.TOKEN_ID).client((String) null).user((String) null).session((String) null).removeDetail(Details.REFRESH_TOKEN_ID).error(Errors.INVALID_CODE).assertEvent(); + events.expectCodeToToken(codeId, sessionId).removeDetail(Details.TOKEN_ID).user((String) null).session((String) null).removeDetail(Details.REFRESH_TOKEN_ID).error(Errors.INVALID_CODE).assertEvent(); events.clear(); } @@ -222,7 +222,7 @@ public class AccessTokenTest { Assert.assertEquals(400, response.getStatusCode()); AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null); - expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null); + expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).user((String) null); expectedEvent.assertEvent(); events.clear(); @@ -254,7 +254,7 @@ public class AccessTokenTest { Assert.assertEquals(400, response.getStatusCode()); AssertEvents.ExpectedEvent expectedEvent = events.expectCodeToToken(codeId, null); - expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).client((String) null).user((String) null); + expectedEvent.error("invalid_code").removeDetail(Details.TOKEN_ID).removeDetail(Details.REFRESH_TOKEN_ID).user((String) null); expectedEvent.assertEvent(); events.clear(); @@ -335,7 +335,7 @@ public class AccessTokenTest { public void testValidateAccessToken() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); URI validateUri = OIDCLoginProtocolService.validateAccessTokenUrl(builder).build("test"); @@ -392,7 +392,7 @@ public class AccessTokenTest { public void testGrantAccessToken() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); { // test checkSsl @@ -421,6 +421,7 @@ public class AccessTokenTest { { // test null username String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD); form.param("password", "password"); Response response = grantTarget.request() .header(HttpHeaders.AUTHORIZATION, header) @@ -432,6 +433,7 @@ public class AccessTokenTest { { // test no password String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD); form.param("username", "test-user@localhost"); Response response = grantTarget.request() .header(HttpHeaders.AUTHORIZATION, header) @@ -443,6 +445,7 @@ public class AccessTokenTest { { // test invalid password String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD); form.param("username", "test-user@localhost"); form.param("password", "invalid"); Response response = grantTarget.request() @@ -598,7 +601,7 @@ public class AccessTokenTest { public void testTokenMapping() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); { KeycloakSession session = keycloakRule.startSession(); @@ -721,7 +724,8 @@ public class AccessTokenTest { protected Response executeGrantAccessTokenRequest(WebTarget grantTarget) { String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); - form.param("username", "test-user@localhost") + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) + .param("username", "test-user@localhost") .param("password", "password"); return grantTarget.request() .header(HttpHeaders.AUTHORIZATION, header) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java index 4f254307bd..3776c03d99 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java @@ -97,7 +97,7 @@ public class RefreshTokenTest { public void nullRefreshToken() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI uri = OIDCLoginProtocolService.refreshUrl(builder).build("test"); + URI uri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget target = client.target(uri); org.keycloak.representations.AccessTokenResponse tokenResponse = null; @@ -389,10 +389,10 @@ public class RefreshTokenTest { public void testCheckSsl() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI uri = OIDCLoginProtocolService.refreshUrl(builder).build("test"); + URI uri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget refreshTarget = client.target(uri); String refreshToken = null; @@ -452,6 +452,7 @@ public class RefreshTokenTest { protected Response executeRefreshToken(WebTarget refreshTarget, String refreshToken) { String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.REFRESH_TOKEN); form.param("refresh_token", refreshToken); return refreshTarget.request() .header(HttpHeaders.AUTHORIZATION, header) @@ -461,7 +462,8 @@ public class RefreshTokenTest { protected Response executeGrantAccessTokenRequest(WebTarget grantTarget) { String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); - form.param("username", "test-user@localhost") + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) + .param("username", "test-user@localhost") .param("password", "password"); return grantTarget.request() .header(HttpHeaders.AUTHORIZATION, header) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java index ea269c339d..7ab25689cb 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java @@ -181,11 +181,9 @@ public class ResourceOwnerPasswordCredentialsGrantTest { events.expectLogin() .client("resource-owner") .session((String) null) - .detail(Details.AUTH_METHOD, "oauth_credentials") - .detail(Details.RESPONSE_TYPE, "token") - .removeDetail(Details.CODE_ID) - .removeDetail(Details.REDIRECT_URI) + .clearDetails() .error(Errors.INVALID_CLIENT_CREDENTIALS) + .user((String) null) .assertEvent(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java index eb52fbb80a..ce9a574435 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java @@ -24,6 +24,7 @@ package org.keycloak.testsuite.oidc; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; +import org.keycloak.OAuth2Constants; import org.keycloak.models.RealmModel; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.representations.AccessTokenResponse; @@ -68,7 +69,7 @@ public class UserInfoTest { public void testSuccessfulUserInfoRequest() throws Exception { Client client = ClientBuilder.newClient(); UriBuilder builder = UriBuilder.fromUri(org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT); - URI grantUri = OIDCLoginProtocolService.grantAccessTokenUrl(builder).build("test"); + URI grantUri = OIDCLoginProtocolService.tokenUrl(builder).build("test"); WebTarget grantTarget = client.target(grantUri); AccessTokenResponse accessTokenResponse = executeGrantAccessTokenRequest(grantTarget); Response response = executeUserInfoRequest(accessTokenResponse.getToken()); @@ -99,7 +100,8 @@ public class UserInfoTest { private AccessTokenResponse executeGrantAccessTokenRequest(WebTarget grantTarget) { String header = BasicAuthHelper.createHeader("test-app", "password"); Form form = new Form(); - form.param("username", "test-user@localhost") + form.param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.PASSWORD) + .param("username", "test-user@localhost") .param("password", "password"); Response response = grantTarget.request() diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java index be6d79d4e6..cdf61d0331 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/perf/AccessTokenPerfTest.java @@ -136,7 +136,7 @@ public class AccessTokenPerfTest { } public String getLoginFormUrl(String state) { - UriBuilder b = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(baseUrl)); if (responseType != null) { b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType); } @@ -204,7 +204,7 @@ public class AccessTokenPerfTest { String authorization = BasicAuthHelper.createHeader(clientId, "password"); - String res = client.target(OIDCLoginProtocolService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl)).build(realm)).request() + String res = client.target(OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)).build(realm)).request() .header(HttpHeaders.AUTHORIZATION, authorization) .post(Entity.form(form), String.class); count.incrementAndGet(); diff --git a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java index 168456e339..d5d2379e57 100755 --- a/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java +++ b/testsuite/performance-web/src/main/java/org/keycloak/testsuite/performance/web/OAuthClient.java @@ -199,7 +199,7 @@ public class OAuthClient { } public String getLoginFormUrl() { - UriBuilder b = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri(baseUrl)); if (responseType != null) { b.queryParam(OAuth2Constants.RESPONSE_TYPE, responseType); } @@ -216,7 +216,7 @@ public class OAuthClient { } public String getAccessTokenUrl() { - UriBuilder b = OIDCLoginProtocolService.accessCodeToTokenUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } @@ -232,12 +232,12 @@ public class OAuthClient { } public String getResourceOwnerPasswordCredentialGrantUrl() { - UriBuilder b = OIDCLoginProtocolService.grantAccessTokenUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } public String getRefreshTokenUrl() { - UriBuilder b = OIDCLoginProtocolService.refreshUrl(UriBuilder.fromUri(baseUrl)); + UriBuilder b = OIDCLoginProtocolService.tokenUrl(UriBuilder.fromUri(baseUrl)); return b.build(realm).toString(); } diff --git a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java index 7443287087..a93d416793 100755 --- a/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java +++ b/testsuite/proxy/src/test/java/org/keycloak/testsuite/ProxyTest.java @@ -188,7 +188,7 @@ public class ProxyTest { @WebResource protected LoginPage loginPage; - public static final String LOGIN_URL = OIDCLoginProtocolService.loginPageUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); + public static final String LOGIN_URL = OIDCLoginProtocolService.authUrl(UriBuilder.fromUri("http://localhost:8081/auth")).build("demo").toString(); @Test public void testHttp() throws Exception {