From 2998a0fa3570fef4c3224ead152fc8255e6a3388 Mon Sep 17 00:00:00 2001 From: Marko Strukelj Date: Tue, 18 Aug 2015 18:10:39 +0200 Subject: [PATCH 1/6] KEYCLOAK-1777 Column \"USERFEDERA0_.REALMENTITY_ID\" not found on Wildfly 10.0.0.Beta1 --- .../main/java/org/keycloak/models/jpa/entities/RealmEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c3eefabc4b..fbfb700b24 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 @@ -116,7 +116,7 @@ public class RealmEntity { Collection requiredCredentials = new ArrayList(); @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true) - @JoinTable(name="FED_PROVIDERS") + @JoinTable(name="FED_PROVIDERS", joinColumns={ @JoinColumn(name="REALM_ID") }) List userFederationProviders = new ArrayList(); @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") From bd13b8a5fe8ae3781aa3b25784359b3251e464cf Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 20 Aug 2015 10:54:59 +0200 Subject: [PATCH 2/6] KEYCLOAK-1761 Remove address from registration and account management KEYCLOAK-1762 Add example theme that adds address fields to registration, account management and admin console --- .../modules/MigrationFromOlderVersions.xml | 8 ++ examples/themes/README.md | 8 ++ .../resources/META-INF/keycloak-themes.json | 3 + .../theme/address/account/account.ftl | 114 +++++++++++++++ .../theme/address/account/theme.properties | 1 + .../resources/partials/user-attributes.html | 55 ++++++++ .../theme/address/admin/theme.properties | 1 + .../address/login/login-update-profile.ftl | 95 +++++++++++++ .../theme/address/login/register.ftl | 131 ++++++++++++++++++ .../theme/address/login/theme.properties | 1 + .../resources/theme/base/account/account.ftl | 46 ------ .../theme/base/admin/resources/js/app.js | 12 ++ .../admin/resources/js/controllers/users.js | 9 ++ .../partials/user-attribute-entry.html | 38 ----- .../resources/partials/user-attributes.html | 45 ++++++ .../admin/resources/partials/user-detail.html | 2 - .../resources/templates/kc-tabs-user.html | 3 +- .../theme/base/login/login-update-profile.ftl | 47 ------- .../resources/theme/base/login/register.ftl | 44 ------ .../testsuite/account/AccountTest.java | 4 +- .../RequiredActionEmailVerificationTest.java | 2 +- .../RequiredActionMultipleActionsTest.java | 2 +- .../actions/RequiredActionTotpSetupTest.java | 9 +- .../RequiredActionUpdateProfileTest.java | 17 +-- .../broker/AbstractIdentityProviderTest.java | 6 +- .../FederationProvidersIntegrationTest.java | 7 +- .../testsuite/forms/RegisterTest.java | 21 ++- .../pages/AccountUpdateProfilePage.java | 7 - .../pages/LoginUpdateProfilePage.java | 12 +- .../testsuite/pages/RegisterPage.java | 14 +- 30 files changed, 514 insertions(+), 250 deletions(-) create mode 100755 examples/themes/src/main/resources/theme/address/account/account.ftl create mode 100644 examples/themes/src/main/resources/theme/address/account/theme.properties create mode 100755 examples/themes/src/main/resources/theme/address/admin/resources/partials/user-attributes.html create mode 100644 examples/themes/src/main/resources/theme/address/admin/theme.properties create mode 100755 examples/themes/src/main/resources/theme/address/login/login-update-profile.ftl create mode 100755 examples/themes/src/main/resources/theme/address/login/register.ftl create mode 100644 examples/themes/src/main/resources/theme/address/login/theme.properties delete mode 100755 forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-attribute-entry.html create mode 100755 forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html diff --git a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml index a49d432c41..10027b90ac 100755 --- a/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml +++ b/docbook/reference/en/en-US/modules/MigrationFromOlderVersions.xml @@ -104,6 +104,14 @@ impacts on performance. + + Contact details removed from registration and account management + + In the default theme we have now removed the contact details from the registration page and account management. The admin console now lists + all the users attributes, not just contact specific attributes. The admin console also has the ability to add/remove attributes to a user. + If you want to add contact details, please refer to the address theme included in the examples. + +
Migrating to 1.3.0.Final diff --git a/examples/themes/README.md b/examples/themes/README.md index 69c922b598..ba47b3c47a 100644 --- a/examples/themes/README.md +++ b/examples/themes/README.md @@ -26,6 +26,14 @@ Then open $KEYCLOAK_HOME/standalone/configuration/keycloak-server.json and regis } +Address Theme +------------------- + +Example theme that adds address fields to registration page, account management and admin console. To enable the theme open the admin console, select your realm, click on `Theme`. In the dropdown for `Login Theme` and `Account Theme` select `address`. Click `Save` and login to the realm to see the new theme in action. + +One thing to note is that to change the admin console for the master admin console (`/auth/admin`) you need to change the theme for the master realm. Changing the admin console theme for any other realms will only change the admin console for that specific realm (for example `/auth/admin/myrealm/console`). + + Sunrise Login Theme ------------------- diff --git a/examples/themes/src/main/resources/META-INF/keycloak-themes.json b/examples/themes/src/main/resources/META-INF/keycloak-themes.json index 12ee694ecd..aafb6e883c 100644 --- a/examples/themes/src/main/resources/META-INF/keycloak-themes.json +++ b/examples/themes/src/main/resources/META-INF/keycloak-themes.json @@ -1,5 +1,8 @@ { "themes": [{ + "name" : "address", + "types": [ "admin", "account", "login" ] + }, { "name" : "logo-example", "types": [ "admin", "account", "login", "welcome" ] }, { diff --git a/examples/themes/src/main/resources/theme/address/account/account.ftl b/examples/themes/src/main/resources/theme/address/account/account.ftl new file mode 100755 index 0000000000..d2a6af16e0 --- /dev/null +++ b/examples/themes/src/main/resources/theme/address/account/account.ftl @@ -0,0 +1,114 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='account' bodyClass='user'; section> + +
+
+

${msg("editAccountHtmlTtile")}

+
+
+ * ${msg("requiredFields")} +
+
+ +
+ + + +
+
+ <#if realm.editUsernameAllowed>* +
+ +
+ disabled="disabled" value="${(account.username!'')?html}"/> +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ + + + + \ No newline at end of file diff --git a/examples/themes/src/main/resources/theme/address/account/theme.properties b/examples/themes/src/main/resources/theme/address/account/theme.properties new file mode 100644 index 0000000000..512d63324a --- /dev/null +++ b/examples/themes/src/main/resources/theme/address/account/theme.properties @@ -0,0 +1 @@ +parent=keycloak \ No newline at end of file diff --git a/examples/themes/src/main/resources/theme/address/admin/resources/partials/user-attributes.html b/examples/themes/src/main/resources/theme/address/admin/resources/partials/user-attributes.html new file mode 100755 index 0000000000..8547961846 --- /dev/null +++ b/examples/themes/src/main/resources/theme/address/admin/resources/partials/user-attributes.html @@ -0,0 +1,55 @@ +
+ + + + +
+
+ +
+ +
+ Street address. +
+
+ +
+ +
+ City or locality. +
+
+ +
+ +
+ State, province, prefecture, or region. +
+
+ +
+ +
+ Zip code or postal code. +
+
+ +
+ +
+ Country name. +
+ +
+
+ + +
+
+
+
+ + diff --git a/examples/themes/src/main/resources/theme/address/admin/theme.properties b/examples/themes/src/main/resources/theme/address/admin/theme.properties new file mode 100644 index 0000000000..512d63324a --- /dev/null +++ b/examples/themes/src/main/resources/theme/address/admin/theme.properties @@ -0,0 +1 @@ +parent=keycloak \ No newline at end of file diff --git a/examples/themes/src/main/resources/theme/address/login/login-update-profile.ftl b/examples/themes/src/main/resources/theme/address/login/login-update-profile.ftl new file mode 100755 index 0000000000..8be620d18f --- /dev/null +++ b/examples/themes/src/main/resources/theme/address/login/login-update-profile.ftl @@ -0,0 +1,95 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "title"> + ${msg("loginProfileTitle")} + <#elseif section = "header"> + ${msg("loginProfileTitle")} + <#elseif section = "form"> +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ + +
+
+
+
+
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/examples/themes/src/main/resources/theme/address/login/register.ftl b/examples/themes/src/main/resources/theme/address/login/register.ftl new file mode 100755 index 0000000000..3247305cf9 --- /dev/null +++ b/examples/themes/src/main/resources/theme/address/login/register.ftl @@ -0,0 +1,131 @@ +<#import "template.ftl" as layout> +<@layout.registrationLayout; section> + <#if section = "title"> + ${msg("registerWithTitle",(realm.name!''))} + <#elseif section = "header"> + ${msg("registerWithTitleHtml",(realm.name!''))} + <#elseif section = "form"> +
+ <#if !realm.registrationEmailAsUsername> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + <#if passwordRequired> +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+ +
+ +
+
+ <#if recaptchaRequired??> +
+
+
+
+
+ + +
+ + +
+ +
+
+
+ + \ No newline at end of file diff --git a/examples/themes/src/main/resources/theme/address/login/theme.properties b/examples/themes/src/main/resources/theme/address/login/theme.properties new file mode 100644 index 0000000000..512d63324a --- /dev/null +++ b/examples/themes/src/main/resources/theme/address/login/theme.properties @@ -0,0 +1 @@ +parent=keycloak \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/account/account.ftl b/forms/common-themes/src/main/resources/theme/base/account/account.ftl index d2a6af16e0..60a21b5e14 100755 --- a/forms/common-themes/src/main/resources/theme/base/account/account.ftl +++ b/forms/common-themes/src/main/resources/theme/base/account/account.ftl @@ -54,52 +54,6 @@ -
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index 2a37bc0c63..d0fc4024f8 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -345,6 +345,18 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'UserDetailCtrl' }) + .when('/realms/:realm/users/:user/user-attributes', { + templateUrl : resourceUrl + '/partials/user-attributes.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + } + }, + controller : 'UserDetailCtrl' + }) .when('/realms/:realm/users/:user/user-credentials', { templateUrl : resourceUrl + '/partials/user-credentials.html', resolve : { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index 802b202528..b693827a21 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -450,6 +450,15 @@ module.controller('UserDetailCtrl', function($scope, realm, user, BruteForceUser $scope.cancel = function() { $location.url("/realms/" + realm.realm + "/users"); }; + + $scope.addAttribute = function() { + $scope.user.attributes[$scope.newAttribute.key] = $scope.newAttribute.value; + delete $scope.newAttribute; + } + + $scope.removeAttribute = function(key) { + delete $scope.user.attributes[key]; + } }); module.controller('UserCredentialsCtrl', function($scope, realm, user, User, UserCredentials, Notifications, Dialog) { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-attribute-entry.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-attribute-entry.html deleted file mode 100755 index 373c91c13d..0000000000 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-attribute-entry.html +++ /dev/null @@ -1,38 +0,0 @@ -
- Contact Information Expand this section to configure user's contact information. -
- -
- -
- Street address. -
-
- -
- -
- City or locality. -
-
- -
- -
- State, province, prefecture, or region. -
-
- -
- -
- Zip code or postal code. -
-
- -
- -
- Country name. -
-
\ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html new file mode 100755 index 0000000000..542431dcf6 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-attributes.html @@ -0,0 +1,45 @@ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + +
KeyValueActions
{{key}} + +
+ +
+ +
+
+ + +
+
+
+
+ + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html index 006f83f98a..feedea5a4c 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html @@ -123,8 +123,6 @@ -
-
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html index e090fca31e..edc3a66481 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html @@ -6,7 +6,8 @@

Add User

- -
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
diff --git a/forms/common-themes/src/main/resources/theme/base/login/register.ftl b/forms/common-themes/src/main/resources/theme/base/login/register.ftl index 3247305cf9..d1593daedf 100755 --- a/forms/common-themes/src/main/resources/theme/base/login/register.ftl +++ b/forms/common-themes/src/main/resources/theme/base/login/register.ftl @@ -62,51 +62,7 @@
-
-
- -
-
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
-
-
- -
- -
- -
-
<#if recaptchaRequired??>
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 12ec02905d..95d644eb82 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 @@ -562,7 +562,7 @@ public class AccountTest { loginPage.open(); loginPage.clickRegister(); - registerPage.register("view", "log", "view-log@localhost", "view-log", "password", "password", null); + registerPage.register("view", "log", "view-log@localhost", "view-log", "password", "password"); expectedEvents.add(events.poll()); expectedEvents.add(events.poll()); @@ -609,7 +609,7 @@ public class AccountTest { loginPage.open(); loginPage.clickRegister(); - registerPage.register("view", "sessions", "view-sessions@localhost", "view-sessions", "password", "password", null); + registerPage.register("view", "sessions", "view-sessions@localhost", "view-sessions", "password", "password"); Event registerEvent = events.expectRegister("view-sessions", "view-sessions@localhost").assertEvent(); String userId = registerEvent.getUserId(); 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 38760b9dc2..a9c0c58373 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 @@ -145,7 +145,7 @@ public class RequiredActionEmailVerificationTest { public void verifyEmailRegister() throws IOException, MessagingException { loginPage.open(); loginPage.clickRegister(); - registerPage.register("firstName", "lastName", "email@mail.com", "verifyEmail", "password", "password", null); + registerPage.register("firstName", "lastName", "email@mail.com", "verifyEmail", "password", "password"); String userId = events.expectRegister("verifyEmail", "email@mail.com").assertEvent().getUserId(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java index 341a440204..4d16a1b1dc 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java @@ -121,7 +121,7 @@ public class RequiredActionMultipleActionsTest { } public String updateProfile(String sessionId) { - updateProfilePage.update("New first", "New last", "new@email.com", null); + updateProfilePage.update("New first", "New last", "new@email.com"); AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_PROFILE); if (sessionId != null) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java index a4206aadd0..dab94288d4 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java @@ -25,21 +25,16 @@ import org.junit.Assert; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; -import org.keycloak.constants.KerberosConstants; import org.keycloak.events.Details; import org.keycloak.events.Event; import org.keycloak.events.EventType; -import org.keycloak.models.ClientModel; import org.keycloak.models.OTPPolicy; -import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredActionProviderModel; -import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.HmacOTP; import org.keycloak.models.utils.TimeBasedOTP; -import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.AssertEvents; @@ -117,7 +112,7 @@ public class RequiredActionTotpSetupTest { public void setupTotpRegister() { loginPage.open(); loginPage.clickRegister(); - registerPage.register("firstName", "lastName", "email@mail.com", "setupTotp", "password", "password", null); + registerPage.register("firstName", "lastName", "email@mail.com", "setupTotp", "password", "password"); String userId = events.expectRegister("setupTotp", "email@mail.com").assertEvent().getUserId(); @@ -170,7 +165,7 @@ public class RequiredActionTotpSetupTest { // Register new user loginPage.open(); loginPage.clickRegister(); - registerPage.register("firstName2", "lastName2", "email2@mail.com", "setupTotp2", "password2", "password2", null); + registerPage.register("firstName2", "lastName2", "email2@mail.com", "setupTotp2", "password2", "password2"); String userId = events.expectRegister("setupTotp2", "email2@mail.com").assertEvent().getUserId(); 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 b61ee176ba..a7c84a0723 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 @@ -87,7 +87,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - updateProfilePage.update("New first", "New last", "new@email.com", "mystreet"); + updateProfilePage.update("New first", "New last", "new@email.com"); String sessionId = events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent().getSessionId(); events.expectRequiredAction(EventType.UPDATE_EMAIL).session(sessionId).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent(); @@ -101,7 +101,6 @@ public class RequiredActionUpdateProfileTest { Assert.assertEquals("New first", user.getFirstName()); Assert.assertEquals("New last", user.getLastName()); Assert.assertEquals("new@email.com", user.getEmail()); - Assert.assertEquals("mystreet", user.getAttributesAsListValues().get("street").get(0)); } @Test @@ -112,7 +111,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - updateProfilePage.update("", "New last", "new@email.com", "mystreet"); + updateProfilePage.update("", "New last", "new@email.com"); updateProfilePage.assertCurrent(); @@ -120,7 +119,6 @@ public class RequiredActionUpdateProfileTest { Assert.assertEquals("", updateProfilePage.getFirstName()); Assert.assertEquals("New last", updateProfilePage.getLastName()); Assert.assertEquals("new@email.com", updateProfilePage.getEmail()); - Assert.assertEquals("mystreet", updateProfilePage.getAttributeStreet()); Assert.assertEquals("Please specify first name.", updateProfilePage.getError()); @@ -135,7 +133,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - updateProfilePage.update("New first", "", "new@email.com", null); + updateProfilePage.update("New first", "", "new@email.com"); updateProfilePage.assertCurrent(); @@ -143,7 +141,6 @@ public class RequiredActionUpdateProfileTest { Assert.assertEquals("New first", updateProfilePage.getFirstName()); Assert.assertEquals("", updateProfilePage.getLastName()); Assert.assertEquals("new@email.com", updateProfilePage.getEmail()); - Assert.assertEquals("", updateProfilePage.getAttributeStreet()); Assert.assertEquals("Please specify last name.", updateProfilePage.getError()); @@ -158,7 +155,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - updateProfilePage.update("New first", "New last", "", "mystreet"); + updateProfilePage.update("New first", "New last", ""); updateProfilePage.assertCurrent(); @@ -166,7 +163,6 @@ public class RequiredActionUpdateProfileTest { Assert.assertEquals("New first", updateProfilePage.getFirstName()); Assert.assertEquals("New last", updateProfilePage.getLastName()); Assert.assertEquals("", updateProfilePage.getEmail()); - Assert.assertEquals("mystreet", updateProfilePage.getAttributeStreet()); Assert.assertEquals("Please specify email.", updateProfilePage.getError()); @@ -181,7 +177,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - updateProfilePage.update("New first", "New last", "invalidemail", null); + updateProfilePage.update("New first", "New last", "invalidemail"); updateProfilePage.assertCurrent(); @@ -189,7 +185,6 @@ public class RequiredActionUpdateProfileTest { Assert.assertEquals("New first", updateProfilePage.getFirstName()); Assert.assertEquals("New last", updateProfilePage.getLastName()); Assert.assertEquals("invalidemail", updateProfilePage.getEmail()); - Assert.assertEquals("", updateProfilePage.getAttributeStreet()); Assert.assertEquals("Invalid email address.", updateProfilePage.getError()); @@ -204,7 +199,7 @@ public class RequiredActionUpdateProfileTest { updateProfilePage.assertCurrent(); - updateProfilePage.update("New first", "New last", "keycloak-user@localhost", null); + updateProfilePage.update("New first", "New last", "keycloak-user@localhost"); updateProfilePage.assertCurrent(); 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 e3dabc8f42..8ce5c7ce6b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -451,7 +451,7 @@ public abstract class AbstractIdentityProviderTest { doAfterProviderAuthentication(); this.updateProfilePage.assertCurrent(); - this.updateProfilePage.update("Test", "User", "psilva@redhat.com", null); + this.updateProfilePage.update("Test", "User", "psilva@redhat.com"); WebElement element = this.driver.findElement(By.className("kc-feedback-text")); @@ -460,7 +460,7 @@ public abstract class AbstractIdentityProviderTest { assertEquals("Email already exists.", element.getText()); this.updateProfilePage.assertCurrent(); - this.updateProfilePage.update("Test", "User", "test-user@redhat.com", null); + this.updateProfilePage.update("Test", "User", "test-user@redhat.com"); assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); @@ -724,7 +724,7 @@ public abstract class AbstractIdentityProviderTest { // update profile this.updateProfilePage.assertCurrent(); - this.updateProfilePage.update(userFirstName, userLastName, userEmail, null); + this.updateProfilePage.update(userFirstName, userLastName, userEmail); } } 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 943adaff47..3a860115c7 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 @@ -245,7 +245,6 @@ public class FederationProvidersIntegrationTest { Assert.assertEquals("John", profilePage.getFirstName()); Assert.assertEquals("Doe", profilePage.getLastName()); Assert.assertEquals("john@email.org", profilePage.getEmail()); - Assert.assertEquals("1234", profilePage.getPostalCode()); } @Test @@ -296,12 +295,12 @@ public class FederationProvidersIntegrationTest { registerPage.assertCurrent(); // check existing username - registerPage.register("firstName", "lastName", "email@mail.cz", "existing", "Password1", "Password1", null); + registerPage.register("firstName", "lastName", "email@mail.cz", "existing", "Password1", "Password1"); registerPage.assertCurrent(); Assert.assertEquals("Username already exists.", registerPage.getError()); // Check existing email - registerPage.register("firstName", "lastName", "existing@email.org", "nonExisting", "Password1", "Password1", null); + registerPage.register("firstName", "lastName", "existing@email.org", "nonExisting", "Password1", "Password1"); registerPage.assertCurrent(); Assert.assertEquals("Email already exists.", registerPage.getError()); } @@ -312,7 +311,7 @@ public class FederationProvidersIntegrationTest { loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1", null); + registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1"); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); KeycloakSession session = keycloakRule.startSession(); 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 63a754eb6f..9c2852e59d 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java @@ -77,7 +77,7 @@ public class RegisterTest { loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", "registerExistingUser@email", "test-user@localhost", "password", "password", "mystreet"); + registerPage.register("firstName", "lastName", "registerExistingUser@email", "test-user@localhost", "password", "password"); registerPage.assertCurrent(); Assert.assertEquals("Username already exists.", registerPage.getError()); @@ -89,7 +89,6 @@ public class RegisterTest { Assert.assertEquals("", registerPage.getUsername()); Assert.assertEquals("", registerPage.getPassword()); Assert.assertEquals("", registerPage.getPasswordConfirm()); - Assert.assertEquals("mystreet", registerPage.getAttributeStreet()); events.expectRegister("test-user@localhost", "registerExistingUser@email") .removeDetail(Details.EMAIL) @@ -102,7 +101,7 @@ public class RegisterTest { loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", "registerUserInvalidPasswordConfirm@email", "registerUserInvalidPasswordConfirm", "password", "invalid", null); + registerPage.register("firstName", "lastName", "registerUserInvalidPasswordConfirm@email", "registerUserInvalidPasswordConfirm", "password", "invalid"); registerPage.assertCurrent(); Assert.assertEquals("Password confirmation doesn't match.", registerPage.getError()); @@ -114,7 +113,6 @@ public class RegisterTest { Assert.assertEquals("registerUserInvalidPasswordConfirm", registerPage.getUsername()); Assert.assertEquals("", registerPage.getPassword()); Assert.assertEquals("", registerPage.getPasswordConfirm()); - Assert.assertEquals("", registerPage.getAttributeStreet()); events.expectRegister("registerUserInvalidPasswordConfirm", "registerUserInvalidPasswordConfirm@email") .removeDetail(Details.USERNAME) @@ -128,7 +126,7 @@ public class RegisterTest { loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", "registerUserMissingPassword@email", "registerUserMissingPassword", null, null, null); + registerPage.register("firstName", "lastName", "registerUserMissingPassword@email", "registerUserMissingPassword", null, null); registerPage.assertCurrent(); Assert.assertEquals("Please specify password.", registerPage.getError()); @@ -153,7 +151,7 @@ public class RegisterTest { loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", "registerPasswordPolicy@email", "registerPasswordPolicy", "pass", "pass", null); + registerPage.register("firstName", "lastName", "registerPasswordPolicy@email", "registerPasswordPolicy", "pass", "pass"); registerPage.assertCurrent(); Assert.assertEquals("Invalid password: minimum length 8.", registerPage.getError()); @@ -163,7 +161,7 @@ public class RegisterTest { .removeDetail(Details.EMAIL) .user((String) null).error("invalid_registration").assertEvent(); - registerPage.register("firstName", "lastName", "registerPasswordPolicy@email", "registerPasswordPolicy", "password", "password", null); + registerPage.register("firstName", "lastName", "registerPasswordPolicy@email", "registerPasswordPolicy", "password", "password"); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); String userId = events.expectRegister("registerPasswordPolicy", "registerPasswordPolicy@email").assertEvent().getUserId(); @@ -185,7 +183,7 @@ public class RegisterTest { loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", "registerUserMissingUsername@email", null, "password", "password", null); + registerPage.register("firstName", "lastName", "registerUserMissingUsername@email", null, "password", "password"); registerPage.assertCurrent(); Assert.assertEquals("Please specify username.", registerPage.getError()); @@ -202,14 +200,14 @@ public class RegisterTest { loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", null, "registerUserMissingEmail", "password", "password", null); + registerPage.register("firstName", "lastName", null, "registerUserMissingEmail", "password", "password"); registerPage.assertCurrent(); 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", null); + registerPage.register("firstName", "lastName", "registerUserInvalidEmailemail", "registerUserInvalidEmail", "password", "password"); registerPage.assertCurrent(); Assert.assertEquals("Invalid email address.", registerPage.getError()); events.expectRegister("registerUserInvalidEmail", "registerUserInvalidEmailemail") @@ -222,7 +220,7 @@ public class RegisterTest { loginPage.clickRegister(); registerPage.assertCurrent(); - registerPage.register("firstName", "lastName", "registerUserSuccess@email", "registerUserSuccess", "password", "password", "myStreet"); + registerPage.register("firstName", "lastName", "registerUserSuccess@email", "registerUserSuccess", "password", "password"); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); @@ -239,7 +237,6 @@ public class RegisterTest { Assert.assertEquals("registerusersuccess@email", user.getEmail()); Assert.assertEquals("firstName", user.getFirstName()); Assert.assertEquals("lastName", user.getLastName()); - Assert.assertEquals("myStreet", user.getAttribute("street").get(0)); } protected UserModel getUser(String userId) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java index 18bc795701..c013345048 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountUpdateProfilePage.java @@ -47,9 +47,6 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { @FindBy(id = "email") private WebElement emailInput; - @FindBy(id = "user.attributes.postal_code") - private WebElement postalCodeInput; - @FindBy(id = "referrer") private WebElement backToApplicationLink; @@ -107,10 +104,6 @@ public class AccountUpdateProfilePage extends AbstractAccountPage { return lastNameInput.getAttribute("value"); } - public String getPostalCode() { - return postalCodeInput.getAttribute("value"); - } - public String getEmail() { return emailInput.getAttribute("value"); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java index 809150f6f9..d67862c792 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java @@ -38,25 +38,19 @@ public class LoginUpdateProfilePage extends AbstractPage { @FindBy(id = "email") private WebElement emailInput; - @FindBy(id = "user.attributes.street") - private WebElement attributeStreetInput; - @FindBy(css = "input[type=\"submit\"]") private WebElement submitButton; @FindBy(className = "feedback-error") private WebElement loginErrorMessage; - public void update(String firstName, String lastName, String email, String attributeStreet) { + public void update(String firstName, String lastName, String email) { firstNameInput.clear(); firstNameInput.sendKeys(firstName); lastNameInput.clear(); lastNameInput.sendKeys(lastName); emailInput.clear(); emailInput.sendKeys(email); - attributeStreetInput.clear(); - if (attributeStreet != null) - attributeStreetInput.sendKeys(attributeStreet); submitButton.click(); } @@ -76,10 +70,6 @@ public class LoginUpdateProfilePage extends AbstractPage { return emailInput.getAttribute("value"); } - public String getAttributeStreet() { - return attributeStreetInput.getAttribute("value"); - } - public boolean isCurrent() { return driver.getTitle().equals("Update Account Information"); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java index 0892b0fb72..456d0a8205 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java @@ -50,9 +50,6 @@ public class RegisterPage extends AbstractPage { @FindBy(id = "password-confirm") private WebElement passwordConfirmInput; - - @FindBy(id = "user.attributes.street") - private WebElement attributeStreetInput; @FindBy(css = "input[type=\"submit\"]") private WebElement submitButton; @@ -60,7 +57,7 @@ public class RegisterPage extends AbstractPage { @FindBy(className = "feedback-error") private WebElement loginErrorMessage; - public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm, String attributeStreet) { + public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) { firstNameInput.clear(); if (firstName != null) { firstNameInput.sendKeys(firstName); @@ -91,11 +88,6 @@ public class RegisterPage extends AbstractPage { passwordConfirmInput.sendKeys(passwordConfirm); } - attributeStreetInput.clear(); - if (attributeStreet != null) { - attributeStreetInput.sendKeys(attributeStreet); - } - submitButton.click(); } @@ -163,10 +155,6 @@ public class RegisterPage extends AbstractPage { return passwordConfirmInput.getAttribute("value"); } - public String getAttributeStreet() { - return attributeStreetInput.getAttribute("value"); - } - public boolean isCurrent() { return driver.getTitle().equals("Register with test"); } From 02958249235c558e828d99b209aeebf426f6489a Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 20 Aug 2015 13:29:32 +0200 Subject: [PATCH 3/6] KEYCLOAK-1747 Deal with time inconsistency in keycloak.js --- .../en/en-US/modules/javascript-adapter.xml | 1 + .../js-console/src/main/webapp/index.html | 10 +- .../js-console/src/main/webapp/keycloak.js | 886 ++++++++++++++++++ integration/js/src/main/resources/keycloak.js | 16 +- 4 files changed, 907 insertions(+), 6 deletions(-) create mode 100755 examples/js-console/src/main/webapp/keycloak.js diff --git a/docbook/reference/en/en-US/modules/javascript-adapter.xml b/docbook/reference/en/en-US/modules/javascript-adapter.xml index 996f29a8a8..2e9f941ed8 100755 --- a/docbook/reference/en/en-US/modules/javascript-adapter.xml +++ b/docbook/reference/en/en-US/modules/javascript-adapter.xml @@ -177,6 +177,7 @@ new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp' resourceAccess - the resource roles assocaited with the token refreshToken - the base64 encoded token that can be used to retrieve a new token refreshTokenParsed - the parsed refresh token + timeSkew - estimated skew between local time and Keycloak server in seconds
diff --git a/examples/js-console/src/main/webapp/index.html b/examples/js-console/src/main/webapp/index.html index 153053b53e..d85b63b811 100644 --- a/examples/js-console/src/main/webapp/index.html +++ b/examples/js-console/src/main/webapp/index.html @@ -49,7 +49,7 @@ if (refreshed) { output(keycloak.tokenParsed); } else { - output('Token not refreshed, valid for ' + Math.round(keycloak.tokenParsed.exp - new Date().getTime() / 1000) + ' seconds'); + output('Token not refreshed, valid for ' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds'); } }).error(function() { output('Failed to refresh token'); @@ -62,11 +62,11 @@ return; } - var o = 'Token Expires:\t\t' + new Date(keycloak.tokenParsed.exp * 1000).toLocaleString() + '\n'; - o += 'Token Expires in:\t' + Math.round(keycloak.tokenParsed.exp - new Date().getTime() / 1000) + ' seconds\n'; + var o = 'Token Expires:\t\t' + new Date((keycloak.tokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n'; + o += 'Token Expires in:\t' + Math.round(keycloak.tokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds\n'; - o += 'Refresh Token Expires:\t' + new Date(keycloak.refreshTokenParsed.exp * 1000).toLocaleString() + '\n'; - o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp - new Date().getTime() / 1000) + ' seconds'; + o += 'Refresh Token Expires:\t' + new Date((keycloak.refreshTokenParsed.exp + keycloak.timeSkew) * 1000).toLocaleString() + '\n'; + o += 'Refresh Expires in:\t' + Math.round(keycloak.refreshTokenParsed.exp + keycloak.timeSkew - new Date().getTime() / 1000) + ' seconds'; output(o); } diff --git a/examples/js-console/src/main/webapp/keycloak.js b/examples/js-console/src/main/webapp/keycloak.js new file mode 100755 index 0000000000..8917581af4 --- /dev/null +++ b/examples/js-console/src/main/webapp/keycloak.js @@ -0,0 +1,886 @@ +(function( window, undefined ) { + + var Keycloak = function (config) { + if (!(this instanceof Keycloak)) { + return new Keycloak(config); + } + + var kc = this; + var adapter; + var refreshQueue = []; + + var loginIframe = { + enable: true, + callbackMap: [], + interval: 5 + }; + + kc.init = function (initOptions) { + kc.authenticated = false; + + if (window.Cordova) { + adapter = loadAdapter('cordova'); + } else { + adapter = loadAdapter(); + } + + if (initOptions) { + if (typeof initOptions.checkLoginIframe !== 'undefined') { + loginIframe.enable = initOptions.checkLoginIframe; + } + + if (initOptions.checkLoginIframeInterval) { + loginIframe.interval = initOptions.checkLoginIframeInterval; + } + + if (initOptions.onLoad === 'login-required') { + kc.loginRequired = true; + } + } + + var promise = createPromise(); + + var initPromise = createPromise(); + initPromise.promise.success(function() { + kc.onReady && kc.onReady(kc.authenticated); + promise.setSuccess(kc.authenticated); + }).error(function() { + promise.setError(); + }); + + var configPromise = loadConfig(config); + + function onLoad() { + var doLogin = function(prompt) { + if (!prompt) { + options.prompt = 'none'; + } + kc.login(options).success(function () { + initPromise.setSuccess(); + }).error(function () { + initPromise.setError(); + }); + } + + var options = {}; + switch (initOptions.onLoad) { + case 'check-sso': + if (loginIframe.enable) { + setupCheckLoginIframe().success(function() { + checkLoginIframe().success(function () { + doLogin(false); + }).error(function () { + initPromise.setSuccess(); + }); + }); + } else { + doLogin(false); + } + break; + case 'login-required': + doLogin(true); + break; + default: + throw 'Invalid value for onLoad'; + } + } + + function processInit() { + var callback = parseCallback(window.location.href); + + if (callback) { + setupCheckLoginIframe(); + window.history.replaceState({}, null, callback.newUrl); + processCallback(callback, initPromise); + return; + } else if (initOptions) { + if (initOptions.token || initOptions.refreshToken) { + setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); + + if (loginIframe.enable) { + setupCheckLoginIframe().success(function() { + checkLoginIframe().success(function () { + initPromise.setSuccess(); + }).error(function () { + if (initOptions.onLoad) { + onLoad(); + } + }); + }); + } else { + initPromise.setSuccess(); + } + } else if (initOptions.onLoad) { + onLoad(); + } + } else { + initPromise.setSuccess(); + } + } + + configPromise.success(processInit); + configPromise.error(function() { + promise.setError(); + }); + + return promise.promise; + } + + kc.login = function (options) { + return adapter.login(options); + } + + kc.createLoginUrl = function(options) { + var state = createUUID(); + + var redirectUri = adapter.redirectUri(options); + if (options && options.prompt) { + redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt; + } + + sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) }); + + var action = 'auth'; + if (options && options.action == 'register') { + action = 'registrations'; + } + + var url = getRealmUrl() + + '/protocol/openid-connect/' + action + + '?client_id=' + encodeURIComponent(kc.clientId) + + '&redirect_uri=' + encodeURIComponent(redirectUri) + + '&state=' + encodeURIComponent(state) + + '&response_type=code'; + + if (options && options.prompt) { + url += '&prompt=' + options.prompt; + } + + if (options && options.loginHint) { + url += '&login_hint=' + options.loginHint; + } + + if (options && options.idpHint) { + url += '&kc_idp_hint=' + options.idpHint; + } + + return url; + } + + kc.logout = function(options) { + return adapter.logout(options); + } + + kc.createLogoutUrl = function(options) { + var url = getRealmUrl() + + '/protocol/openid-connect/logout' + + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options)); + + return url; + } + + kc.createAccountUrl = function(options) { + var url = getRealmUrl() + + '/account' + + '?referrer=' + encodeURIComponent(kc.clientId) + + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options)); + + return url; + } + + kc.accountManagement = function() { + return adapter.accountManagement(); + } + + kc.hasRealmRole = function (role) { + var access = kc.realmAccess; + return !!access && access.roles.indexOf(role) >= 0; + } + + kc.hasResourceRole = function(role, resource) { + if (!kc.resourceAccess) { + return false; + } + + var access = kc.resourceAccess[resource || kc.clientId]; + return !!access && access.roles.indexOf(role) >= 0; + } + + kc.loadUserProfile = function() { + var url = getRealmUrl() + '/account'; + var req = new XMLHttpRequest(); + req.open('GET', url, true); + req.setRequestHeader('Accept', 'application/json'); + req.setRequestHeader('Authorization', 'bearer ' + kc.token); + + var promise = createPromise(); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + kc.profile = JSON.parse(req.responseText); + promise.setSuccess(kc.profile); + } else { + promise.setError(); + } + } + } + + req.send(); + + return promise.promise; + } + + kc.loadUserInfo = function() { + var url = getRealmUrl() + '/protocol/openid-connect/userinfo'; + var req = new XMLHttpRequest(); + req.open('GET', url, true); + req.setRequestHeader('Accept', 'application/json'); + req.setRequestHeader('Authorization', 'bearer ' + kc.token); + + var promise = createPromise(); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + kc.userInfo = JSON.parse(req.responseText); + promise.setSuccess(kc.userInfo); + } else { + promise.setError(); + } + } + } + + req.send(); + + return promise.promise; + } + + kc.isTokenExpired = function(minValidity) { + if (!kc.tokenParsed || !kc.refreshToken) { + throw 'Not authenticated'; + } + + var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew; + if (minValidity) { + expiresIn -= minValidity; + } + + return expiresIn < 0; + } + + kc.updateToken = function(minValidity) { + var promise = createPromise(); + + if (!kc.tokenParsed || !kc.refreshToken) { + promise.setError(); + return promise.promise; + } + + minValidity = minValidity || 5; + + var exec = function() { + if (!kc.isTokenExpired(minValidity)) { + promise.setSuccess(false); + } else { + var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; + var url = getRealmUrl() + '/protocol/openid-connect/token'; + + refreshQueue.push(promise); + + if (refreshQueue.length == 1) { + var req = new XMLHttpRequest(); + req.open('POST', url, true); + req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + + if (kc.clientId && kc.clientSecret) { + req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); + } else { + params += '&client_id=' + encodeURIComponent(kc.clientId); + } + + var timeLocal = new Date().getTime(); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + timeLocal = (timeLocal + new Date().getTime()) / 2; + + var tokenResponse = JSON.parse(req.responseText); + setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']); + + kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat; + + kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); + for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { + p.setSuccess(true); + } + } else { + kc.onAuthRefreshError && kc.onAuthRefreshError(); + for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { + p.setError(true); + } + } + } + }; + + req.send(params); + } + } + } + + if (loginIframe.enable) { + var iframePromise = checkLoginIframe(); + iframePromise.success(function() { + exec(); + }).error(function() { + promise.setError(); + }); + } else { + exec(); + } + + return promise.promise; + } + + kc.clearToken = function() { + if (kc.token) { + setToken(null, null, null); + kc.onAuthLogout && kc.onAuthLogout(); + if (kc.loginRequired) { + kc.login(); + } + } + } + + function getRealmUrl() { + if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') { + return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm); + } else { + return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm); + } + } + + function getOrigin() { + if (!window.location.origin) { + return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); + } else { + return window.location.origin; + } + } + + function processCallback(oauth, promise) { + var code = oauth.code; + var error = oauth.error; + var prompt = oauth.prompt; + + if (code) { + var params = 'code=' + code + '&grant_type=authorization_code'; + var url = getRealmUrl() + '/protocol/openid-connect/token'; + + var req = new XMLHttpRequest(); + req.open('POST', url, true); + req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + + if (kc.clientId && kc.clientSecret) { + req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); + } else { + params += '&client_id=' + encodeURIComponent(kc.clientId); + } + + params += '&redirect_uri=' + oauth.redirectUri; + + req.withCredentials = true; + + var timeLocal = new Date().getTime(); + + req.onreadystatechange = function() { + if (req.readyState == 4) { + if (req.status == 200) { + timeLocal = (timeLocal + new Date().getTime()) / 2; + + var tokenResponse = JSON.parse(req.responseText); + setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']); + + kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat; + + kc.onAuthSuccess && kc.onAuthSuccess(); + promise && promise.setSuccess(); + } else { + kc.onAuthError && kc.onAuthError(); + promise && promise.setError(); + } + } + }; + + req.send(params); + } else if (error) { + if (prompt != 'none') { + kc.onAuthError && kc.onAuthError(); + promise && promise.setError(); + } else { + promise && promise.setSuccess(); + } + } + } + + function loadConfig(url) { + var promise = createPromise(); + var configUrl; + + if (!config) { + configUrl = 'keycloak.json'; + } else if (typeof config === 'string') { + configUrl = config; + } + + if (configUrl) { + var req = new XMLHttpRequest(); + req.open('GET', configUrl, true); + req.setRequestHeader('Accept', 'application/json'); + + req.onreadystatechange = function () { + if (req.readyState == 4) { + if (req.status == 200) { + var config = JSON.parse(req.responseText); + + kc.authServerUrl = config['auth-server-url']; + kc.realm = config['realm']; + kc.clientId = config['resource']; + kc.clientSecret = (config['credentials'] || {})['secret']; + + promise.setSuccess(); + } else { + promise.setError(); + } + } + }; + + req.send(); + } else { + if (!config['url']) { + var scripts = document.getElementsByTagName('script'); + for (var i = 0; i < scripts.length; i++) { + if (scripts[i].src.match(/.*keycloak\.js/)) { + config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js')); + break; + } + } + } + + if (!config.realm) { + throw 'realm missing'; + } + + if (!config.clientId) { + throw 'clientId missing'; + } + + kc.authServerUrl = config.url; + kc.realm = config.realm; + kc.clientId = config.clientId; + kc.clientSecret = (config.credentials || {}).secret; + + promise.setSuccess(); + } + + return promise.promise; + } + + function setToken(token, refreshToken, idToken) { + if (token) { + kc.token = token; + kc.tokenParsed = decodeToken(token); + var sessionId = kc.realm + '/' + kc.tokenParsed.sub; + if (kc.tokenParsed.session_state) { + sessionId = sessionId + '/' + kc.tokenParsed.session_state; + } + kc.sessionId = sessionId; + kc.authenticated = true; + kc.subject = kc.tokenParsed.sub; + kc.realmAccess = kc.tokenParsed.realm_access; + kc.resourceAccess = kc.tokenParsed.resource_access; + } else { + delete kc.token; + delete kc.tokenParsed; + delete kc.subject; + delete kc.realmAccess; + delete kc.resourceAccess; + + kc.authenticated = false; + } + + if (refreshToken) { + kc.refreshToken = refreshToken; + kc.refreshTokenParsed = decodeToken(refreshToken); + } else { + delete kc.refreshToken; + delete kc.refreshTokenParsed; + } + + if (idToken) { + kc.idToken = idToken; + kc.idTokenParsed = decodeToken(idToken); + } else { + delete kc.idToken; + delete kc.idTokenParsed; + } + } + + function decodeToken(str) { + str = str.split('.')[1]; + + str = str.replace('/-/g', '+'); + str = str.replace('/_/g', '/'); + switch (str.length % 4) + { + case 0: + break; + case 2: + str += '=='; + break; + case 3: + str += '='; + break; + default: + throw 'Invalid token'; + } + + str = (str + '===').slice(0, str.length + (str.length % 4)); + str = str.replace(/-/g, '+').replace(/_/g, '/'); + + str = decodeURIComponent(escape(atob(str))); + + str = JSON.parse(str); + return str; + } + + function createUUID() { + var s = []; + var hexDigits = '0123456789abcdef'; + for (var i = 0; i < 36; i++) { + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); + } + s[14] = '4'; + s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); + s[8] = s[13] = s[18] = s[23] = '-'; + var uuid = s.join(''); + return uuid; + } + + kc.callback_id = 0; + + function createCallbackId() { + var id = ''; + return id; + + } + + function parseCallback(url) { + if (url.indexOf('?') != -1) { + var oauth = {}; + + oauth.newUrl = url.split('?')[0]; + var paramString = url.split('?')[1]; + var fragIndex = paramString.indexOf('#'); + if (fragIndex != -1) { + paramString = paramString.substring(0, fragIndex); + } + var params = paramString.split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + switch (decodeURIComponent(p[0])) { + case 'code': + oauth.code = p[1]; + break; + case 'error': + oauth.error = p[1]; + break; + case 'state': + oauth.state = decodeURIComponent(p[1]); + break; + case 'redirect_fragment': + oauth.fragment = decodeURIComponent(p[1]); + break; + case 'prompt': + oauth.prompt = p[1]; + break; + default: + oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + p[0] + '=' + p[1]; + break; + } + } + + var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState); + + if (sessionState && (oauth.code || oauth.error) && oauth.state && oauth.state == sessionState.state) { + delete sessionStorage.oauthState; + + oauth.redirectUri = sessionState.redirectUri; + + if (oauth.fragment) { + oauth.newUrl += '#' + oauth.fragment; + } + + return oauth; + } + } + } + + function createPromise() { + var p = { + setSuccess: function(result) { + p.success = true; + p.result = result; + if (p.successCallback) { + p.successCallback(result); + } + }, + + setError: function(result) { + p.error = true; + p.result = result; + if (p.errorCallback) { + p.errorCallback(result); + } + }, + + promise: { + success: function(callback) { + if (p.success) { + callback(p.result); + } else if (!p.error) { + p.successCallback = callback; + } + return p.promise; + }, + error: function(callback) { + if (p.error) { + callback(p.result); + } else if (!p.success) { + p.errorCallback = callback; + } + return p.promise; + } + } + } + return p; + } + + function setupCheckLoginIframe() { + var promise = createPromise(); + + if (!loginIframe.enable) { + promise.setSuccess(); + return promise.promise; + } + + if (loginIframe.iframe) { + promise.setSuccess(); + return promise.promise; + } + + var iframe = document.createElement('iframe'); + loginIframe.iframe = iframe; + + iframe.onload = function() { + var realmUrl = getRealmUrl(); + if (realmUrl.charAt(0) === '/') { + loginIframe.iframeOrigin = getOrigin(); + } else { + loginIframe.iframeOrigin = realmUrl.substring(0, realmUrl.indexOf('/', 8)); + } + promise.setSuccess(); + + setTimeout(check, loginIframe.interval * 1000); + } + + var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId) + '&origin=' + getOrigin(); + iframe.setAttribute('src', src ); + iframe.style.display = 'none'; + document.body.appendChild(iframe); + + var messageCallback = function(event) { + if (event.origin !== loginIframe.iframeOrigin) { + return; + } + var data = JSON.parse(event.data); + var promise = loginIframe.callbackMap[data.callbackId]; + delete loginIframe.callbackMap[data.callbackId]; + + if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) { + promise.setSuccess(); + } else { + kc.clearToken(); + promise.setError(); + } + }; + window.addEventListener('message', messageCallback, false); + + var check = function() { + checkLoginIframe(); + if (kc.token) { + setTimeout(check, loginIframe.interval * 1000); + } + }; + + return promise.promise; + } + + function checkLoginIframe() { + var promise = createPromise(); + + if (loginIframe.iframe && loginIframe.iframeOrigin) { + var msg = {}; + msg.callbackId = createCallbackId(); + loginIframe.callbackMap[msg.callbackId] = promise; + var origin = loginIframe.iframeOrigin; + loginIframe.iframe.contentWindow.postMessage(JSON.stringify(msg), origin); + } else { + promise.setSuccess(); + } + + return promise.promise; + } + + function loadAdapter(type) { + if (!type || type == 'default') { + return { + login: function(options) { + window.location.href = kc.createLoginUrl(options); + return createPromise().promise; + }, + + logout: function(options) { + window.location.href = kc.createLogoutUrl(options); + return createPromise().promise; + }, + + accountManagement : function() { + window.location.href = kc.createAccountUrl(); + return createPromise().promise; + }, + + redirectUri: function(options) { + if (options && options.redirectUri) { + return options.redirectUri; + } else if (kc.redirectUri) { + return kc.redirectUri; + } else { + var redirectUri = location.href; + if (location.hash) { + redirectUri = redirectUri.substring(0, location.href.indexOf('#')); + redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1)); + } + return redirectUri; + } + } + }; + } + + if (type == 'cordova') { + loginIframe.enable = false; + + return { + login: function(options) { + var promise = createPromise(); + + var o = 'location=no'; + if (options && options.prompt == 'none') { + o += ',hidden=yes'; + } + + var loginUrl = kc.createLoginUrl(options); + var ref = window.open(loginUrl, '_blank', o); + + var callback; + var error; + + ref.addEventListener('loadstart', function(event) { + if (event.url.indexOf('http://localhost') == 0) { + callback = parseCallback(event.url); + ref.close(); + } + }); + + ref.addEventListener('loaderror', function(event) { + if (event.url.indexOf('http://localhost') != 0) { + error = true; + ref.close(); + } + }); + + ref.addEventListener('exit', function(event) { + if (error || !callback) { + promise.setError(); + } else { + processCallback(callback, promise); + } + }); + + return promise.promise; + }, + + logout: function(options) { + var promise = createPromise(); + + var logoutUrl = kc.createLogoutUrl(options); + var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes'); + + var error; + + ref.addEventListener('loadstart', function(event) { + if (event.url.indexOf('http://localhost') == 0) { + ref.close(); + } + }); + + ref.addEventListener('loaderror', function(event) { + if (event.url.indexOf('http://localhost') != 0) { + error = true; + ref.close(); + } + }); + + ref.addEventListener('exit', function(event) { + if (error) { + promise.setError(); + } else { + kc.clearToken(); + promise.setSuccess(); + } + }); + + return promise.promise; + }, + + accountManagement : function() { + var accountUrl = kc.createAccountUrl(); + var ref = window.open(accountUrl, '_blank', 'location=no'); + ref.addEventListener('loadstart', function(event) { + if (event.url.indexOf('http://localhost') == 0) { + ref.close(); + } + }); + }, + + redirectUri: function(options) { + return 'http://localhost'; + } + } + } + + throw 'invalid adapter type: ' + type; + } + } + + if ( typeof module === "object" && module && typeof module.exports === "object" ) { + module.exports = Keycloak; + } else { + window.Keycloak = Keycloak; + + if ( typeof define === "function" && define.amd ) { + define( "keycloak", [], function () { return Keycloak; } ); + } + } +})( window ); diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js index 798bfdd74f..8917581af4 100755 --- a/integration/js/src/main/resources/keycloak.js +++ b/integration/js/src/main/resources/keycloak.js @@ -261,7 +261,7 @@ throw 'Not authenticated'; } - var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000); + var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew; if (minValidity) { expiresIn -= minValidity; } @@ -299,11 +299,18 @@ params += '&client_id=' + encodeURIComponent(kc.clientId); } + var timeLocal = new Date().getTime(); + req.onreadystatechange = function () { if (req.readyState == 4) { if (req.status == 200) { + timeLocal = (timeLocal + new Date().getTime()) / 2; + var tokenResponse = JSON.parse(req.responseText); setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']); + + kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat; + kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { p.setSuccess(true); @@ -385,11 +392,18 @@ req.withCredentials = true; + var timeLocal = new Date().getTime(); + req.onreadystatechange = function() { if (req.readyState == 4) { if (req.status == 200) { + timeLocal = (timeLocal + new Date().getTime()) / 2; + var tokenResponse = JSON.parse(req.responseText); setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']); + + kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat; + kc.onAuthSuccess && kc.onAuthSuccess(); promise && promise.setSuccess(); } else { From f827b2dc0d1943b1d2ea5d6b75a14d2a551e2bb4 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 20 Aug 2015 13:37:50 +0200 Subject: [PATCH 4/6] KEYCLOAK-1130 security-deployment vs. secure-deployment in the documentation --- docbook/reference/en/en-US/modules/jboss-adapter.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docbook/reference/en/en-US/modules/jboss-adapter.xml b/docbook/reference/en/en-US/modules/jboss-adapter.xml index 6349c454f8..5ed29795ae 100755 --- a/docbook/reference/en/en-US/modules/jboss-adapter.xml +++ b/docbook/reference/en/en-US/modules/jboss-adapter.xml @@ -235,7 +235,7 @@ public class CustomerService { - The security-deployment name attribute identifies the WAR you want + The secure-deployment name attribute identifies the WAR you want to secure. Its value is the module-name defined in web.xml with .war appended. The rest of the configuration corresponds pretty much one to one with the keycloak.json configuration options defined in general adapter configuration. From 1fb3d7cd3b82c5d4fc3a6106ddd81a2d4f792ff3 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 20 Aug 2015 13:40:35 +0200 Subject: [PATCH 5/6] KEYCLOAK-1161 web.xml example in the doc is incorrect --- docbook/reference/en/en-US/modules/jboss-adapter.xml | 9 +++------ docbook/reference/en/en-US/modules/jetty9-adapter.xml | 6 ------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/docbook/reference/en/en-US/modules/jboss-adapter.xml b/docbook/reference/en/en-US/modules/jboss-adapter.xml index 5ed29795ae..71e599d73b 100755 --- a/docbook/reference/en/en-US/modules/jboss-adapter.xml +++ b/docbook/reference/en/en-US/modules/jboss-adapter.xml @@ -167,6 +167,9 @@ public class CustomerService { admin + + CONFIDENTIAL + @@ -176,12 +179,6 @@ public class CustomerService { user - - - - - /* - CONFIDENTIAL diff --git a/docbook/reference/en/en-US/modules/jetty9-adapter.xml b/docbook/reference/en/en-US/modules/jetty9-adapter.xml index 30e387868e..dea36fd824 100755 --- a/docbook/reference/en/en-US/modules/jetty9-adapter.xml +++ b/docbook/reference/en/en-US/modules/jetty9-adapter.xml @@ -132,12 +132,6 @@ $ java -jar $JETTY_HOME/start.jar --add-to-startd=keycloak user - - - - - /* - CONFIDENTIAL From 5ca3a48094fdbcc60e07ec3919e5753e8a32b82b Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Thu, 20 Aug 2015 14:54:46 +0200 Subject: [PATCH 6/6] KEYCLOAK-1723 Allow aud to be single field or array --- .../broker/oidc/OIDCIdentityProvider.java | 3 +- .../json/StringOrArrayDeserializer.java | 30 + .../json/StringOrArraySerializer.java | 25 + .../keycloak/representations/AccessToken.java | 5 - .../org/keycloak/representations/IDToken.java | 6 - .../representations/JsonWebToken.java | 26 +- .../org/keycloak/util/JsonSerialization.java | 1 - .../org/keycloak/jose/JsonWebTokenTest.java | 44 + .../js-console/src/main/webapp/keycloak.js | 886 ------------------ integration/js/src/main/resources/keycloak.js | 4 +- .../client/JWTClientAuthenticator.java | 8 +- .../resources/IdentityBrokerService.java | 4 +- .../broker/AbstractIdentityProviderTest.java | 4 +- 13 files changed, 128 insertions(+), 918 deletions(-) create mode 100644 core/src/main/java/org/keycloak/json/StringOrArrayDeserializer.java create mode 100644 core/src/main/java/org/keycloak/json/StringOrArraySerializer.java create mode 100644 core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java delete mode 100755 examples/js-console/src/main/webapp/keycloak.js diff --git a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java index c576a5d6cd..e49842bc51 100755 --- a/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java +++ b/broker/oidc/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java @@ -289,10 +289,9 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider { + + @Override + public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + JsonNode jsonNode = jsonParser.readValueAsTree(); + if (jsonNode.isArray()) { + ArrayList a = new ArrayList<>(1); + Iterator itr = jsonNode.iterator(); + while (itr.hasNext()) { + a.add(itr.next().getTextValue()); + } + return a.toArray(new String[a.size()]); + } else { + return new String[] { jsonNode.getTextValue() }; + } + } + +} diff --git a/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java b/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java new file mode 100644 index 0000000000..f9b354766b --- /dev/null +++ b/core/src/main/java/org/keycloak/json/StringOrArraySerializer.java @@ -0,0 +1,25 @@ +package org.keycloak.json; + +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.map.JsonSerializer; +import org.codehaus.jackson.map.SerializerProvider; + +import java.io.IOException; + +public class StringOrArraySerializer extends JsonSerializer { + @Override + public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + String[] array = (String[]) o; + if (array == null) { + jsonGenerator.writeNull(); + } else if (array.length == 1) { + jsonGenerator.writeString(array[0]); + } else { + jsonGenerator.writeStartArray(); + for (String s : array) { + jsonGenerator.writeString(s); + } + jsonGenerator.writeEndArray(); + } + } +} diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java index 1380c2febe..092298e848 100755 --- a/core/src/main/java/org/keycloak/representations/AccessToken.java +++ b/core/src/main/java/org/keycloak/representations/AccessToken.java @@ -163,11 +163,6 @@ public class AccessToken extends IDToken { return (AccessToken) super.issuer(issuer); } - @Override - public AccessToken audience(String audience) { - return (AccessToken) super.audience(audience); - } - @Override public AccessToken subject(String subject) { return (AccessToken) super.subject(subject); diff --git a/core/src/main/java/org/keycloak/representations/IDToken.java b/core/src/main/java/org/keycloak/representations/IDToken.java index 42f767959e..499180bb2f 100755 --- a/core/src/main/java/org/keycloak/representations/IDToken.java +++ b/core/src/main/java/org/keycloak/representations/IDToken.java @@ -1,12 +1,6 @@ package org.keycloak.representations; -import org.codehaus.jackson.annotate.JsonAnyGetter; -import org.codehaus.jackson.annotate.JsonAnySetter; import org.codehaus.jackson.annotate.JsonProperty; -import org.codehaus.jackson.annotate.JsonUnwrapped; - -import java.util.HashMap; -import java.util.Map; /** * @author Bill Burke diff --git a/core/src/main/java/org/keycloak/representations/JsonWebToken.java b/core/src/main/java/org/keycloak/representations/JsonWebToken.java index f714d4ff69..b547fcabe4 100755 --- a/core/src/main/java/org/keycloak/representations/JsonWebToken.java +++ b/core/src/main/java/org/keycloak/representations/JsonWebToken.java @@ -4,6 +4,10 @@ import org.codehaus.jackson.annotate.JsonAnyGetter; import org.codehaus.jackson.annotate.JsonAnySetter; import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.annotate.JsonProperty; +import org.codehaus.jackson.map.annotate.JsonDeserialize; +import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.keycloak.json.StringOrArrayDeserializer; +import org.keycloak.json.StringOrArraySerializer; import org.keycloak.util.Time; import java.io.Serializable; @@ -26,14 +30,16 @@ public class JsonWebToken implements Serializable { @JsonProperty("iss") protected String issuer; @JsonProperty("aud") - protected String audience; + @JsonSerialize(using = StringOrArraySerializer.class) + @JsonDeserialize(using = StringOrArrayDeserializer.class) + protected String[] audience; @JsonProperty("sub") protected String subject; @JsonProperty("typ") protected String type; @JsonProperty("azp") public String issuedFor; - protected Map otherClaims = new HashMap(); + protected Map otherClaims = new HashMap<>(); public String getId() { return id; @@ -72,7 +78,6 @@ public class JsonWebToken implements Serializable { @JsonIgnore public boolean isNotBefore() { return Time.currentTime() >= notBefore; - } /** @@ -113,12 +118,21 @@ public class JsonWebToken implements Serializable { return this; } - - public String getAudience() { + @JsonIgnore + public String[] getAudience() { return audience; } - public JsonWebToken audience(String audience) { + public boolean hasAudience(String audience) { + for (String a : this.audience) { + if (a.equals(audience)) { + return true; + } + } + return false; + } + + public JsonWebToken audience(String... audience) { this.audience = audience; return this; } diff --git a/core/src/main/java/org/keycloak/util/JsonSerialization.java b/core/src/main/java/org/keycloak/util/JsonSerialization.java index ff080def36..a1a93ba1cc 100755 --- a/core/src/main/java/org/keycloak/util/JsonSerialization.java +++ b/core/src/main/java/org/keycloak/util/JsonSerialization.java @@ -3,7 +3,6 @@ package org.keycloak.util; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.SerializationConfig; import org.codehaus.jackson.map.annotate.JsonSerialize; -import org.codehaus.jackson.type.TypeReference; import java.io.IOException; import java.io.InputStream; diff --git a/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java b/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java new file mode 100644 index 0000000000..dbe0ecb690 --- /dev/null +++ b/core/src/test/java/org/keycloak/jose/JsonWebTokenTest.java @@ -0,0 +1,44 @@ +package org.keycloak.jose; + +import org.junit.Test; +import org.keycloak.representations.JsonWebToken; +import org.keycloak.util.JsonSerialization; + +import java.io.IOException; + +import static org.junit.Assert.*; + +/** + * Created by st on 20.08.15. + */ +public class JsonWebTokenTest { + + @Test + public void testAudSingle() throws IOException { + String single = "{ \"aud\": \"test\" }"; + JsonWebToken s = JsonSerialization.readValue(single, JsonWebToken.class); + assertArrayEquals(new String[] { "test" }, s.getAudience()); + } + + @Test + public void testAudArray() throws IOException { + String single = "{ \"aud\": [\"test\"] }"; + JsonWebToken s = JsonSerialization.readValue(single, JsonWebToken.class); + assertArrayEquals(new String[]{"test"}, s.getAudience()); + } + + @Test + public void test() throws IOException { + JsonWebToken jsonWebToken = new JsonWebToken(); + jsonWebToken.audience("test"); + assertTrue(JsonSerialization.writeValueAsPrettyString(jsonWebToken).contains("\"aud\" : \"test\"")); + } + + @Test + public void testArray() throws IOException { + JsonWebToken jsonWebToken = new JsonWebToken(); + jsonWebToken.audience("test", "test2"); + assertTrue(JsonSerialization.writeValueAsPrettyString(jsonWebToken).contains("\"aud\" : [ \"test\", \"test2\" ]")); + } + +} diff --git a/examples/js-console/src/main/webapp/keycloak.js b/examples/js-console/src/main/webapp/keycloak.js deleted file mode 100755 index 8917581af4..0000000000 --- a/examples/js-console/src/main/webapp/keycloak.js +++ /dev/null @@ -1,886 +0,0 @@ -(function( window, undefined ) { - - var Keycloak = function (config) { - if (!(this instanceof Keycloak)) { - return new Keycloak(config); - } - - var kc = this; - var adapter; - var refreshQueue = []; - - var loginIframe = { - enable: true, - callbackMap: [], - interval: 5 - }; - - kc.init = function (initOptions) { - kc.authenticated = false; - - if (window.Cordova) { - adapter = loadAdapter('cordova'); - } else { - adapter = loadAdapter(); - } - - if (initOptions) { - if (typeof initOptions.checkLoginIframe !== 'undefined') { - loginIframe.enable = initOptions.checkLoginIframe; - } - - if (initOptions.checkLoginIframeInterval) { - loginIframe.interval = initOptions.checkLoginIframeInterval; - } - - if (initOptions.onLoad === 'login-required') { - kc.loginRequired = true; - } - } - - var promise = createPromise(); - - var initPromise = createPromise(); - initPromise.promise.success(function() { - kc.onReady && kc.onReady(kc.authenticated); - promise.setSuccess(kc.authenticated); - }).error(function() { - promise.setError(); - }); - - var configPromise = loadConfig(config); - - function onLoad() { - var doLogin = function(prompt) { - if (!prompt) { - options.prompt = 'none'; - } - kc.login(options).success(function () { - initPromise.setSuccess(); - }).error(function () { - initPromise.setError(); - }); - } - - var options = {}; - switch (initOptions.onLoad) { - case 'check-sso': - if (loginIframe.enable) { - setupCheckLoginIframe().success(function() { - checkLoginIframe().success(function () { - doLogin(false); - }).error(function () { - initPromise.setSuccess(); - }); - }); - } else { - doLogin(false); - } - break; - case 'login-required': - doLogin(true); - break; - default: - throw 'Invalid value for onLoad'; - } - } - - function processInit() { - var callback = parseCallback(window.location.href); - - if (callback) { - setupCheckLoginIframe(); - window.history.replaceState({}, null, callback.newUrl); - processCallback(callback, initPromise); - return; - } else if (initOptions) { - if (initOptions.token || initOptions.refreshToken) { - setToken(initOptions.token, initOptions.refreshToken, initOptions.idToken); - - if (loginIframe.enable) { - setupCheckLoginIframe().success(function() { - checkLoginIframe().success(function () { - initPromise.setSuccess(); - }).error(function () { - if (initOptions.onLoad) { - onLoad(); - } - }); - }); - } else { - initPromise.setSuccess(); - } - } else if (initOptions.onLoad) { - onLoad(); - } - } else { - initPromise.setSuccess(); - } - } - - configPromise.success(processInit); - configPromise.error(function() { - promise.setError(); - }); - - return promise.promise; - } - - kc.login = function (options) { - return adapter.login(options); - } - - kc.createLoginUrl = function(options) { - var state = createUUID(); - - var redirectUri = adapter.redirectUri(options); - if (options && options.prompt) { - redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt; - } - - sessionStorage.oauthState = JSON.stringify({ state: state, redirectUri: encodeURIComponent(redirectUri) }); - - var action = 'auth'; - if (options && options.action == 'register') { - action = 'registrations'; - } - - var url = getRealmUrl() - + '/protocol/openid-connect/' + action - + '?client_id=' + encodeURIComponent(kc.clientId) - + '&redirect_uri=' + encodeURIComponent(redirectUri) - + '&state=' + encodeURIComponent(state) - + '&response_type=code'; - - if (options && options.prompt) { - url += '&prompt=' + options.prompt; - } - - if (options && options.loginHint) { - url += '&login_hint=' + options.loginHint; - } - - if (options && options.idpHint) { - url += '&kc_idp_hint=' + options.idpHint; - } - - return url; - } - - kc.logout = function(options) { - return adapter.logout(options); - } - - kc.createLogoutUrl = function(options) { - var url = getRealmUrl() - + '/protocol/openid-connect/logout' - + '?redirect_uri=' + encodeURIComponent(adapter.redirectUri(options)); - - return url; - } - - kc.createAccountUrl = function(options) { - var url = getRealmUrl() - + '/account' - + '?referrer=' + encodeURIComponent(kc.clientId) - + '&referrer_uri=' + encodeURIComponent(adapter.redirectUri(options)); - - return url; - } - - kc.accountManagement = function() { - return adapter.accountManagement(); - } - - kc.hasRealmRole = function (role) { - var access = kc.realmAccess; - return !!access && access.roles.indexOf(role) >= 0; - } - - kc.hasResourceRole = function(role, resource) { - if (!kc.resourceAccess) { - return false; - } - - var access = kc.resourceAccess[resource || kc.clientId]; - return !!access && access.roles.indexOf(role) >= 0; - } - - kc.loadUserProfile = function() { - var url = getRealmUrl() + '/account'; - var req = new XMLHttpRequest(); - req.open('GET', url, true); - req.setRequestHeader('Accept', 'application/json'); - req.setRequestHeader('Authorization', 'bearer ' + kc.token); - - var promise = createPromise(); - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - kc.profile = JSON.parse(req.responseText); - promise.setSuccess(kc.profile); - } else { - promise.setError(); - } - } - } - - req.send(); - - return promise.promise; - } - - kc.loadUserInfo = function() { - var url = getRealmUrl() + '/protocol/openid-connect/userinfo'; - var req = new XMLHttpRequest(); - req.open('GET', url, true); - req.setRequestHeader('Accept', 'application/json'); - req.setRequestHeader('Authorization', 'bearer ' + kc.token); - - var promise = createPromise(); - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - kc.userInfo = JSON.parse(req.responseText); - promise.setSuccess(kc.userInfo); - } else { - promise.setError(); - } - } - } - - req.send(); - - return promise.promise; - } - - kc.isTokenExpired = function(minValidity) { - if (!kc.tokenParsed || !kc.refreshToken) { - throw 'Not authenticated'; - } - - var expiresIn = kc.tokenParsed['exp'] - (new Date().getTime() / 1000) + kc.timeSkew; - if (minValidity) { - expiresIn -= minValidity; - } - - return expiresIn < 0; - } - - kc.updateToken = function(minValidity) { - var promise = createPromise(); - - if (!kc.tokenParsed || !kc.refreshToken) { - promise.setError(); - return promise.promise; - } - - minValidity = minValidity || 5; - - var exec = function() { - if (!kc.isTokenExpired(minValidity)) { - promise.setSuccess(false); - } else { - var params = 'grant_type=refresh_token&' + 'refresh_token=' + kc.refreshToken; - var url = getRealmUrl() + '/protocol/openid-connect/token'; - - refreshQueue.push(promise); - - if (refreshQueue.length == 1) { - var req = new XMLHttpRequest(); - req.open('POST', url, true); - req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - - if (kc.clientId && kc.clientSecret) { - req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); - } else { - params += '&client_id=' + encodeURIComponent(kc.clientId); - } - - var timeLocal = new Date().getTime(); - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - timeLocal = (timeLocal + new Date().getTime()) / 2; - - var tokenResponse = JSON.parse(req.responseText); - setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']); - - kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat; - - kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); - for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { - p.setSuccess(true); - } - } else { - kc.onAuthRefreshError && kc.onAuthRefreshError(); - for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { - p.setError(true); - } - } - } - }; - - req.send(params); - } - } - } - - if (loginIframe.enable) { - var iframePromise = checkLoginIframe(); - iframePromise.success(function() { - exec(); - }).error(function() { - promise.setError(); - }); - } else { - exec(); - } - - return promise.promise; - } - - kc.clearToken = function() { - if (kc.token) { - setToken(null, null, null); - kc.onAuthLogout && kc.onAuthLogout(); - if (kc.loginRequired) { - kc.login(); - } - } - } - - function getRealmUrl() { - if (kc.authServerUrl.charAt(kc.authServerUrl.length - 1) == '/') { - return kc.authServerUrl + 'realms/' + encodeURIComponent(kc.realm); - } else { - return kc.authServerUrl + '/realms/' + encodeURIComponent(kc.realm); - } - } - - function getOrigin() { - if (!window.location.origin) { - return window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); - } else { - return window.location.origin; - } - } - - function processCallback(oauth, promise) { - var code = oauth.code; - var error = oauth.error; - var prompt = oauth.prompt; - - if (code) { - var params = 'code=' + code + '&grant_type=authorization_code'; - var url = getRealmUrl() + '/protocol/openid-connect/token'; - - var req = new XMLHttpRequest(); - req.open('POST', url, true); - req.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); - - if (kc.clientId && kc.clientSecret) { - req.setRequestHeader('Authorization', 'Basic ' + btoa(kc.clientId + ':' + kc.clientSecret)); - } else { - params += '&client_id=' + encodeURIComponent(kc.clientId); - } - - params += '&redirect_uri=' + oauth.redirectUri; - - req.withCredentials = true; - - var timeLocal = new Date().getTime(); - - req.onreadystatechange = function() { - if (req.readyState == 4) { - if (req.status == 200) { - timeLocal = (timeLocal + new Date().getTime()) / 2; - - var tokenResponse = JSON.parse(req.responseText); - setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']); - - kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat; - - kc.onAuthSuccess && kc.onAuthSuccess(); - promise && promise.setSuccess(); - } else { - kc.onAuthError && kc.onAuthError(); - promise && promise.setError(); - } - } - }; - - req.send(params); - } else if (error) { - if (prompt != 'none') { - kc.onAuthError && kc.onAuthError(); - promise && promise.setError(); - } else { - promise && promise.setSuccess(); - } - } - } - - function loadConfig(url) { - var promise = createPromise(); - var configUrl; - - if (!config) { - configUrl = 'keycloak.json'; - } else if (typeof config === 'string') { - configUrl = config; - } - - if (configUrl) { - var req = new XMLHttpRequest(); - req.open('GET', configUrl, true); - req.setRequestHeader('Accept', 'application/json'); - - req.onreadystatechange = function () { - if (req.readyState == 4) { - if (req.status == 200) { - var config = JSON.parse(req.responseText); - - kc.authServerUrl = config['auth-server-url']; - kc.realm = config['realm']; - kc.clientId = config['resource']; - kc.clientSecret = (config['credentials'] || {})['secret']; - - promise.setSuccess(); - } else { - promise.setError(); - } - } - }; - - req.send(); - } else { - if (!config['url']) { - var scripts = document.getElementsByTagName('script'); - for (var i = 0; i < scripts.length; i++) { - if (scripts[i].src.match(/.*keycloak\.js/)) { - config.url = scripts[i].src.substr(0, scripts[i].src.indexOf('/js/keycloak.js')); - break; - } - } - } - - if (!config.realm) { - throw 'realm missing'; - } - - if (!config.clientId) { - throw 'clientId missing'; - } - - kc.authServerUrl = config.url; - kc.realm = config.realm; - kc.clientId = config.clientId; - kc.clientSecret = (config.credentials || {}).secret; - - promise.setSuccess(); - } - - return promise.promise; - } - - function setToken(token, refreshToken, idToken) { - if (token) { - kc.token = token; - kc.tokenParsed = decodeToken(token); - var sessionId = kc.realm + '/' + kc.tokenParsed.sub; - if (kc.tokenParsed.session_state) { - sessionId = sessionId + '/' + kc.tokenParsed.session_state; - } - kc.sessionId = sessionId; - kc.authenticated = true; - kc.subject = kc.tokenParsed.sub; - kc.realmAccess = kc.tokenParsed.realm_access; - kc.resourceAccess = kc.tokenParsed.resource_access; - } else { - delete kc.token; - delete kc.tokenParsed; - delete kc.subject; - delete kc.realmAccess; - delete kc.resourceAccess; - - kc.authenticated = false; - } - - if (refreshToken) { - kc.refreshToken = refreshToken; - kc.refreshTokenParsed = decodeToken(refreshToken); - } else { - delete kc.refreshToken; - delete kc.refreshTokenParsed; - } - - if (idToken) { - kc.idToken = idToken; - kc.idTokenParsed = decodeToken(idToken); - } else { - delete kc.idToken; - delete kc.idTokenParsed; - } - } - - function decodeToken(str) { - str = str.split('.')[1]; - - str = str.replace('/-/g', '+'); - str = str.replace('/_/g', '/'); - switch (str.length % 4) - { - case 0: - break; - case 2: - str += '=='; - break; - case 3: - str += '='; - break; - default: - throw 'Invalid token'; - } - - str = (str + '===').slice(0, str.length + (str.length % 4)); - str = str.replace(/-/g, '+').replace(/_/g, '/'); - - str = decodeURIComponent(escape(atob(str))); - - str = JSON.parse(str); - return str; - } - - function createUUID() { - var s = []; - var hexDigits = '0123456789abcdef'; - for (var i = 0; i < 36; i++) { - s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); - } - s[14] = '4'; - s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); - s[8] = s[13] = s[18] = s[23] = '-'; - var uuid = s.join(''); - return uuid; - } - - kc.callback_id = 0; - - function createCallbackId() { - var id = ''; - return id; - - } - - function parseCallback(url) { - if (url.indexOf('?') != -1) { - var oauth = {}; - - oauth.newUrl = url.split('?')[0]; - var paramString = url.split('?')[1]; - var fragIndex = paramString.indexOf('#'); - if (fragIndex != -1) { - paramString = paramString.substring(0, fragIndex); - } - var params = paramString.split('&'); - for (var i = 0; i < params.length; i++) { - var p = params[i].split('='); - switch (decodeURIComponent(p[0])) { - case 'code': - oauth.code = p[1]; - break; - case 'error': - oauth.error = p[1]; - break; - case 'state': - oauth.state = decodeURIComponent(p[1]); - break; - case 'redirect_fragment': - oauth.fragment = decodeURIComponent(p[1]); - break; - case 'prompt': - oauth.prompt = p[1]; - break; - default: - oauth.newUrl += (oauth.newUrl.indexOf('?') == -1 ? '?' : '&') + p[0] + '=' + p[1]; - break; - } - } - - var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState); - - if (sessionState && (oauth.code || oauth.error) && oauth.state && oauth.state == sessionState.state) { - delete sessionStorage.oauthState; - - oauth.redirectUri = sessionState.redirectUri; - - if (oauth.fragment) { - oauth.newUrl += '#' + oauth.fragment; - } - - return oauth; - } - } - } - - function createPromise() { - var p = { - setSuccess: function(result) { - p.success = true; - p.result = result; - if (p.successCallback) { - p.successCallback(result); - } - }, - - setError: function(result) { - p.error = true; - p.result = result; - if (p.errorCallback) { - p.errorCallback(result); - } - }, - - promise: { - success: function(callback) { - if (p.success) { - callback(p.result); - } else if (!p.error) { - p.successCallback = callback; - } - return p.promise; - }, - error: function(callback) { - if (p.error) { - callback(p.result); - } else if (!p.success) { - p.errorCallback = callback; - } - return p.promise; - } - } - } - return p; - } - - function setupCheckLoginIframe() { - var promise = createPromise(); - - if (!loginIframe.enable) { - promise.setSuccess(); - return promise.promise; - } - - if (loginIframe.iframe) { - promise.setSuccess(); - return promise.promise; - } - - var iframe = document.createElement('iframe'); - loginIframe.iframe = iframe; - - iframe.onload = function() { - var realmUrl = getRealmUrl(); - if (realmUrl.charAt(0) === '/') { - loginIframe.iframeOrigin = getOrigin(); - } else { - loginIframe.iframeOrigin = realmUrl.substring(0, realmUrl.indexOf('/', 8)); - } - promise.setSuccess(); - - setTimeout(check, loginIframe.interval * 1000); - } - - var src = getRealmUrl() + '/protocol/openid-connect/login-status-iframe.html?client_id=' + encodeURIComponent(kc.clientId) + '&origin=' + getOrigin(); - iframe.setAttribute('src', src ); - iframe.style.display = 'none'; - document.body.appendChild(iframe); - - var messageCallback = function(event) { - if (event.origin !== loginIframe.iframeOrigin) { - return; - } - var data = JSON.parse(event.data); - var promise = loginIframe.callbackMap[data.callbackId]; - delete loginIframe.callbackMap[data.callbackId]; - - if ((!kc.sessionId || kc.sessionId == data.session) && data.loggedIn) { - promise.setSuccess(); - } else { - kc.clearToken(); - promise.setError(); - } - }; - window.addEventListener('message', messageCallback, false); - - var check = function() { - checkLoginIframe(); - if (kc.token) { - setTimeout(check, loginIframe.interval * 1000); - } - }; - - return promise.promise; - } - - function checkLoginIframe() { - var promise = createPromise(); - - if (loginIframe.iframe && loginIframe.iframeOrigin) { - var msg = {}; - msg.callbackId = createCallbackId(); - loginIframe.callbackMap[msg.callbackId] = promise; - var origin = loginIframe.iframeOrigin; - loginIframe.iframe.contentWindow.postMessage(JSON.stringify(msg), origin); - } else { - promise.setSuccess(); - } - - return promise.promise; - } - - function loadAdapter(type) { - if (!type || type == 'default') { - return { - login: function(options) { - window.location.href = kc.createLoginUrl(options); - return createPromise().promise; - }, - - logout: function(options) { - window.location.href = kc.createLogoutUrl(options); - return createPromise().promise; - }, - - accountManagement : function() { - window.location.href = kc.createAccountUrl(); - return createPromise().promise; - }, - - redirectUri: function(options) { - if (options && options.redirectUri) { - return options.redirectUri; - } else if (kc.redirectUri) { - return kc.redirectUri; - } else { - var redirectUri = location.href; - if (location.hash) { - redirectUri = redirectUri.substring(0, location.href.indexOf('#')); - redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'redirect_fragment=' + encodeURIComponent(location.hash.substring(1)); - } - return redirectUri; - } - } - }; - } - - if (type == 'cordova') { - loginIframe.enable = false; - - return { - login: function(options) { - var promise = createPromise(); - - var o = 'location=no'; - if (options && options.prompt == 'none') { - o += ',hidden=yes'; - } - - var loginUrl = kc.createLoginUrl(options); - var ref = window.open(loginUrl, '_blank', o); - - var callback; - var error; - - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - callback = parseCallback(event.url); - ref.close(); - } - }); - - ref.addEventListener('loaderror', function(event) { - if (event.url.indexOf('http://localhost') != 0) { - error = true; - ref.close(); - } - }); - - ref.addEventListener('exit', function(event) { - if (error || !callback) { - promise.setError(); - } else { - processCallback(callback, promise); - } - }); - - return promise.promise; - }, - - logout: function(options) { - var promise = createPromise(); - - var logoutUrl = kc.createLogoutUrl(options); - var ref = window.open(logoutUrl, '_blank', 'location=no,hidden=yes'); - - var error; - - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } - }); - - ref.addEventListener('loaderror', function(event) { - if (event.url.indexOf('http://localhost') != 0) { - error = true; - ref.close(); - } - }); - - ref.addEventListener('exit', function(event) { - if (error) { - promise.setError(); - } else { - kc.clearToken(); - promise.setSuccess(); - } - }); - - return promise.promise; - }, - - accountManagement : function() { - var accountUrl = kc.createAccountUrl(); - var ref = window.open(accountUrl, '_blank', 'location=no'); - ref.addEventListener('loadstart', function(event) { - if (event.url.indexOf('http://localhost') == 0) { - ref.close(); - } - }); - }, - - redirectUri: function(options) { - return 'http://localhost'; - } - } - } - - throw 'invalid adapter type: ' + type; - } - } - - if ( typeof module === "object" && module && typeof module.exports === "object" ) { - module.exports = Keycloak; - } else { - window.Keycloak = Keycloak; - - if ( typeof define === "function" && define.amd ) { - define( "keycloak", [], function () { return Keycloak; } ); - } - } -})( window ); diff --git a/integration/js/src/main/resources/keycloak.js b/integration/js/src/main/resources/keycloak.js index 8917581af4..46d3b18559 100755 --- a/integration/js/src/main/resources/keycloak.js +++ b/integration/js/src/main/resources/keycloak.js @@ -309,7 +309,7 @@ var tokenResponse = JSON.parse(req.responseText); setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']); - kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat; + kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat; kc.onAuthRefreshSuccess && kc.onAuthRefreshSuccess(); for (var p = refreshQueue.pop(); p != null; p = refreshQueue.pop()) { @@ -402,7 +402,7 @@ var tokenResponse = JSON.parse(req.responseText); setToken(tokenResponse['access_token'], tokenResponse['refresh_token'], tokenResponse['id_token']); - kc.timeSkew = Math.floor(timeLocal / 1000) - keycloak.tokenParsed.iat; + kc.timeSkew = Math.floor(timeLocal / 1000) - kc.tokenParsed.iat; kc.onAuthSuccess && kc.onAuthSuccess(); promise && promise.setSuccess(); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java index 5191ed0ec1..3a98c8d33b 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java @@ -111,13 +111,9 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator { } // Validate other things - String audience = token.getAudience(); String expectedAudience = Urls.realmIssuer(context.getUriInfo().getBaseUri(), realm.getName()); - if (audience == null) { - throw new RuntimeException("Audience is null on JWT"); - } - if (!audience.equals(expectedAudience)) { - throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + audience + "'"); + if (!token.hasAudience(expectedAudience)) { + throw new RuntimeException("Token audience doesn't match domain. Realm audience is '" + expectedAudience + "' but audience from token is '" + token.getAudience() + "'"); } if (!token.isActive()) { 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 c5a7f8f0ac..15c5f3800c 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -190,8 +190,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal if (authResult != null) { AccessToken token = authResult.getToken(); - String audience = token.getAudience(); - ClientModel clientModel = this.realmModel.getClientByClientId(audience); + String[] audience = token.getAudience(); + ClientModel clientModel = this.realmModel.getClientByClientId(audience[0]); if (clientModel == null) { return badRequest("Invalid client."); 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 8ce5c7ce6b..fb2b5dfd4b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -48,6 +48,7 @@ import org.keycloak.testsuite.pages.VerifyEmailPage; import org.keycloak.testsuite.rule.GreenMailRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.util.JsonSerialization; import org.openqa.selenium.By; import org.openqa.selenium.NoSuchElementException; import org.openqa.selenium.WebDriver; @@ -801,10 +802,9 @@ public abstract class AbstractIdentityProviderTest { UserSessionStatus sessionStatus = null; try { - ObjectMapper objectMapper = new ObjectMapper(); String pageSource = this.driver.getPageSource(); - sessionStatus = objectMapper.readValue(pageSource.getBytes(), UserSessionStatus.class); + sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatus.class); } catch (IOException ignore) { ignore.printStackTrace(); }