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

View file

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

View file

@ -23,6 +23,7 @@
<div>
<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.register()">Register</button>
<button onclick="keycloak.accountManagement()">Account</button>
@ -155,6 +156,17 @@
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
var initOptions = {
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.onAuthLogout = function () {event('Auth Logout')}");
jsExecutor.executeScript("window.keycloak.onTokenExpired = function () {event('Access token expired.')}");
jsExecutor.executeScript("window.keycloak.onActionUpdate = function (status) {event('AIA status: ' + status)}");
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_SPACE_URL = "/auth/realms/Example realm/testing/javascript";
public static int TOKEN_LIFESPAN_LEEWAY = 3; // seconds
public static final String USER_PASSWORD = "password";
protected JavascriptExecutor jsExecutor;
@ -80,8 +81,8 @@ public abstract class AbstractJavascriptTest extends AbstractAuthTest {
public static final UserRepresentation unauthorizedUser;
static {
testUser = UserBuilder.create().username("test-user@localhost").password("password").build();
unauthorizedUser = UserBuilder.create().username("unauthorized").password("password").build();
testUser = UserBuilder.create().username("test-user@localhost").password(USER_PASSWORD).build();
unauthorizedUser = UserBuilder.create().username("unauthorized").password(USER_PASSWORD).build();
}
@BeforeClass

View file

@ -20,6 +20,7 @@ import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.auth.page.account.Applications;
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.OAuthClient;
import org.keycloak.testsuite.util.RealmBuilder;
@ -77,6 +78,10 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
@JavascriptBrowser
private OAuthGrant oAuthGrantPage;
@Page
@JavascriptBrowser
private UpdatePassword updatePasswordPage;
@Override
protected RealmRepresentation updateRealm(RealmBuilder builder) {
return builder.accessTokenLifespan(30 + TOKEN_LIFESPAN_LEEWAY).build();
@ -660,4 +665,36 @@ public class JavascriptAdapterTest extends AbstractJavascriptTest {
.init(defaultArguments(), this::assertSuccessfullyLoggedIn)
.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");
});
}
}