KEYCLOAK-13565 Add support for kc_action to keycloak.js

Co-authored-by mhajas <mhajas@redhat.com>
This commit is contained in:
stianst 2020-03-25 08:11:12 +01:00 committed by Stian Thorgersen
parent 97b5654690
commit 1f02f87a6e
6 changed files with 70 additions and 6 deletions

View file

@ -173,7 +173,7 @@ declare namespace Keycloak {
* If value is `'register'` then user is redirected to registration page, * If value is `'register'` then user is redirected to registration page,
* otherwise to login page. * otherwise to login page.
*/ */
action?: 'register'; action?: string;
/** /**
* Used just if user is already authenticated. Specifies maximum time since * Used just if user is already authenticated. Specifies maximum time since
@ -433,6 +433,11 @@ declare namespace Keycloak {
*/ */
onTokenExpired?(): void; onTokenExpired?(): void;
/**
* Called when a AIA has been requested by the application.
*/
onActionUpdate?(status: 'success'|'cancelled'|'error'): void;
/** /**
* Called to initialize the adapter. * Called to initialize the adapter.
* @param initOptions Initialization options. * @param initOptions Initialization options.

View file

@ -476,6 +476,10 @@
url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint); url += '&kc_idp_hint=' + encodeURIComponent(options.idpHint);
} }
if (options && options.action && options.action != 'register') {
url += '&kc_action=' + encodeURIComponent(options.action);
}
if (options && options.locale) { if (options && options.locale) {
url += '&ui_locales=' + encodeURIComponent(options.locale); url += '&ui_locales=' + encodeURIComponent(options.locale);
} }
@ -740,6 +744,10 @@
var timeLocal = new Date().getTime(); var timeLocal = new Date().getTime();
if (oauth['kc_action_status']) {
kc.onActionUpdate && kc.onActionUpdate(oauth['kc_action_status']);
}
if (error) { if (error) {
if (prompt != 'none') { if (prompt != 'none') {
var errorData = { error: error, error_description: oauth.error_description }; var errorData = { error: error, error_description: oauth.error_description };
@ -1085,13 +1093,13 @@
var supportedParams; var supportedParams;
switch (kc.flow) { switch (kc.flow) {
case 'standard': case 'standard':
supportedParams = ['code', 'state', 'session_state']; supportedParams = ['code', 'state', 'session_state', 'kc_action_status'];
break; break;
case 'implicit': case 'implicit':
supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in']; supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status'];
break; break;
case 'hybrid': case 'hybrid':
supportedParams = ['access_token', 'id_token', 'code', 'state', 'session_state']; supportedParams = ['access_token', 'id_token', 'code', 'state', 'session_state', 'kc_action_status'];
break; break;
} }

View file

@ -23,6 +23,7 @@
<div> <div>
<button onclick="keycloak.login()">Login</button> <button onclick="keycloak.login()">Login</button>
<button onclick="keycloak.login({ action: 'UPDATE_PASSWORD' })">Update Password</button>
<button onclick="keycloak.logout()">Logout</button> <button onclick="keycloak.logout()">Logout</button>
<button onclick="keycloak.register()">Register</button> <button onclick="keycloak.register()">Register</button>
<button onclick="keycloak.accountManagement()">Account</button> <button onclick="keycloak.accountManagement()">Account</button>
@ -155,6 +156,17 @@
event('Access token expired.'); event('Access token expired.');
}; };
keycloak.onActionUpdate = function (status) {
switch (status) {
case 'success':
event('Action completed successfully'); break;
case 'cancelled':
event('Action cancelled by user'); break;
case 'error':
event('Action failed'); break;
}
};
// Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too // Flow can be changed to 'implicit' or 'hybrid', but then client must enable implicit flow in admin console too
var initOptions = { var initOptions = {
responseMode: 'fragment', responseMode: 'fragment',

View file

@ -114,6 +114,7 @@ public class JavascriptTestExecutor {
jsExecutor.executeScript("window.keycloak.onAuthRefreshError = function () {event('Auth Refresh Error')}"); jsExecutor.executeScript("window.keycloak.onAuthRefreshError = function () {event('Auth Refresh Error')}");
jsExecutor.executeScript("window.keycloak.onAuthLogout = function () {event('Auth Logout')}"); jsExecutor.executeScript("window.keycloak.onAuthLogout = function () {event('Auth Logout')}");
jsExecutor.executeScript("window.keycloak.onTokenExpired = function () {event('Access token expired.')}"); jsExecutor.executeScript("window.keycloak.onTokenExpired = function () {event('Access token expired.')}");
jsExecutor.executeScript("window.keycloak.onActionUpdate = function (status) {event('AIA status: ' + status)}");
configured = true; configured = true;

View file

@ -55,6 +55,7 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest {
public static final String JAVASCRIPT_ENCODED_SPACE_URL = "/auth/realms/Example%20realm/testing/javascript"; public static final String JAVASCRIPT_ENCODED_SPACE_URL = "/auth/realms/Example%20realm/testing/javascript";
public static final String JAVASCRIPT_SPACE_URL = "/auth/realms/Example realm/testing/javascript"; public static final String JAVASCRIPT_SPACE_URL = "/auth/realms/Example realm/testing/javascript";
public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
public static final String USER_PASSWORD = "password";
protected JavascriptExecutor jsExecutor; protected JavascriptExecutor jsExecutor;
@ -80,8 +81,8 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest {
public static final UserRepresentation unauthorizedUser; public static final UserRepresentation unauthorizedUser;
static { static {
testUser = UserBuilder.create().username("test-user@localhost").password("password").build(); testUser = UserBuilder.create().username("test-user@localhost").password(USER_PASSWORD).build();
unauthorizedUser = UserBuilder.create().username("unauthorized").password("password").build(); unauthorizedUser = UserBuilder.create().username("unauthorized").password(USER_PASSWORD).build();
} }
@BeforeClass @BeforeClass

View file

@ -20,6 +20,7 @@ import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.auth.page.account.Applications; import org.keycloak.testsuite.auth.page.account.Applications;
import org.keycloak.testsuite.auth.page.login.OAuthGrant; import org.keycloak.testsuite.auth.page.login.OAuthGrant;
import org.keycloak.testsuite.auth.page.login.UpdatePassword;
import org.keycloak.testsuite.util.JavascriptBrowser; import org.keycloak.testsuite.util.JavascriptBrowser;
import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
@ -77,6 +78,10 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
@JavascriptBrowser @JavascriptBrowser
private OAuthGrant oAuthGrantPage; private OAuthGrant oAuthGrantPage;
@Page
@JavascriptBrowser
private UpdatePassword updatePasswordPage;
@Override @Override
protected RealmRepresentation updateRealm(RealmBuilder builder) { protected RealmRepresentation updateRealm(RealmBuilder builder) {
return builder.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY).build(); return builder.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY).build();
@ -660,4 +665,36 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
.init(defaultArguments(), this::assertSuccessfullyLoggedIn) .init(defaultArguments(), this::assertSuccessfullyLoggedIn)
.executeAsyncScript(refreshWithDeprecatedHandles, assertOutputContains("Success handle")); .executeAsyncScript(refreshWithDeprecatedHandles, assertOutputContains("Success handle"));
} }
@Test
public void testAIAFromJavascriptAdapterSuccess() {
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login(JSObjectBuilder.create()
.add("action", "UPDATE_PASSWORD")
.build(), this::assertOnLoginPage)
.loginForm(testUser);
updatePasswordPage.updatePasswords(USER_PASSWORD, USER_PASSWORD);
testExecutor.init(defaultArguments(), (driver1, output, events1) -> {
assertSuccessfullyLoggedIn(driver1, output, events1);
waitUntilElement(events1).text().contains("AIA status: success");
});
}
@Test
public void testAIAFromJavascriptAdapterCancelled() {
testExecutor.init(defaultArguments(), this::assertInitNotAuth)
.login(JSObjectBuilder.create()
.add("action", "UPDATE_PASSWORD")
.build(), this::assertOnLoginPage)
.loginForm(testUser);
updatePasswordPage.cancel();
testExecutor.init(defaultArguments(), (driver1, output, events1) -> {
assertSuccessfullyLoggedIn(driver1, output, events1);
waitUntilElement(events1).text().contains("AIA status: cancelled");
});
}
} }