Add kc_action
to redirect URI after a required action is cancelled (#31925)
Closes #31894 Signed-off-by: Thomas Darimont <thomas.darimont@googlemail.com>
This commit is contained in:
parent
dad4477995
commit
88a5c96fff
7 changed files with 38 additions and 12 deletions
|
@ -12,7 +12,10 @@ and then is immediately redirected back to the application. However, AIA allows
|
||||||
done even if the user is already authenticated on the client and has an active SSO session. It is triggered by adding the `kc_action` parameter to the OIDC login URL with the value containing the requested action.
|
done even if the user is already authenticated on the client and has an active SSO session. It is triggered by adding the `kc_action` parameter to the OIDC login URL with the value containing the requested action.
|
||||||
For instance `kc_action=UPDATE_PASSWORD` parameter.
|
For instance `kc_action=UPDATE_PASSWORD` parameter.
|
||||||
|
|
||||||
NOTE: The `kc_action` parameter is a {project_name} proprietary mechanism unsupported by the OIDC specification.
|
A user may cancel an application initiated action. In this case the user is redirected back to the client application.
|
||||||
|
The redirect URI will contain the query parameters `kc_action_status=cancelled` and `kc_action` with the name of the cancelled action.
|
||||||
|
|
||||||
|
NOTE: The `kc_action` and `kc_action_status` parameters are a {project_name} proprietary mechanism unsupported by the OIDC specification.
|
||||||
|
|
||||||
NOTE: Application initiated actions are supported only for OIDC clients.
|
NOTE: Application initiated actions are supported only for OIDC clients.
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,13 @@ It used to be difficult to regain access to a {project_name} instance when all a
|
||||||
|
|
||||||
Consequently, the environment variables `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` have been deprecated. You should use `KC_BOOTSTRAP_ADMIN_USERNAME` and `KC_BOOTSTRAP_ADMIN_PASSWORD` instead. These are also general options, so they may be specified via the cli or other config sources, for example `--bootstrap-admin-username=admin`. For more information, see the new https://www.keycloak.org/server/bootstrap-admin-recovery[Bootstrap admin and recovery] guide.
|
Consequently, the environment variables `KEYCLOAK_ADMIN` and `KEYCLOAK_ADMIN_PASSWORD` have been deprecated. You should use `KC_BOOTSTRAP_ADMIN_USERNAME` and `KC_BOOTSTRAP_ADMIN_PASSWORD` instead. These are also general options, so they may be specified via the cli or other config sources, for example `--bootstrap-admin-username=admin`. For more information, see the new https://www.keycloak.org/server/bootstrap-admin-recovery[Bootstrap admin and recovery] guide.
|
||||||
|
|
||||||
|
= Application Initiated Required Action redirect now contains kc_action Parameter
|
||||||
|
|
||||||
|
The required action provider name is now returned via the `kc_action` parameter when redirecting back from an application initiated required action execution.
|
||||||
|
This eases the detection of which required action was executed for a client. The outcome of the execution can be determined via the `kc_action_status` parameter.
|
||||||
|
|
||||||
|
Note: This feature required changes to the Keycloak JS adapter, therefore it is recommended to upgrade to the latest version of the adapter if you want to make use of this feature.
|
||||||
|
|
||||||
= Deprecations in `keycloak-services` module
|
= Deprecations in `keycloak-services` module
|
||||||
|
|
||||||
The class `UserSessionCrossDCManager` is deprecated and planned to be removed in a future version of {project_name}.
|
The class `UserSessionCrossDCManager` is deprecated and planned to be removed in a future version of {project_name}.
|
||||||
|
|
4
js/libs/keycloak-js/dist/keycloak.d.ts
vendored
4
js/libs/keycloak-js/dist/keycloak.d.ts
vendored
|
@ -536,8 +536,10 @@ declare class Keycloak {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a AIA has been requested by the application.
|
* Called when a AIA has been requested by the application.
|
||||||
|
* @param status the outcome of the required action
|
||||||
|
* @param action the alias name of the required action, e.g. UPDATE_PASSWORD, CONFIGURE_TOTP etc.
|
||||||
*/
|
*/
|
||||||
onActionUpdate?(status: 'success'|'cancelled'|'error'): void;
|
onActionUpdate?(status: 'success'|'cancelled'|'error', action?: string): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to initialize the adapter.
|
* Called to initialize the adapter.
|
||||||
|
|
|
@ -751,7 +751,7 @@ function Keycloak (config) {
|
||||||
var timeLocal = new Date().getTime();
|
var timeLocal = new Date().getTime();
|
||||||
|
|
||||||
if (oauth['kc_action_status']) {
|
if (oauth['kc_action_status']) {
|
||||||
kc.onActionUpdate && kc.onActionUpdate(oauth['kc_action_status']);
|
kc.onActionUpdate && kc.onActionUpdate(oauth['kc_action_status'], oauth['kc_action']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -1080,13 +1080,13 @@ function Keycloak (config) {
|
||||||
var supportedParams;
|
var supportedParams;
|
||||||
switch (kc.flow) {
|
switch (kc.flow) {
|
||||||
case 'standard':
|
case 'standard':
|
||||||
supportedParams = ['code', 'state', 'session_state', 'kc_action_status', 'iss'];
|
supportedParams = ['code', 'state', 'session_state', 'kc_action_status', 'kc_action', 'iss'];
|
||||||
break;
|
break;
|
||||||
case 'implicit':
|
case 'implicit':
|
||||||
supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status', 'iss'];
|
supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status', 'kc_action', 'iss'];
|
||||||
break;
|
break;
|
||||||
case 'hybrid':
|
case 'hybrid':
|
||||||
supportedParams = ['access_token', 'token_type', 'id_token', 'code', 'state', 'session_state', 'expires_in', 'kc_action_status', 'iss'];
|
supportedParams = ['access_token', 'token_type', 'id_token', 'code', 'state', 'session_state', 'expires_in', 'kc_action_status', 'kc_action', 'iss'];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1441,7 +1441,7 @@ function Keycloak (config) {
|
||||||
var getCordovaRedirectUri = function() {
|
var getCordovaRedirectUri = function() {
|
||||||
return kc.redirectUri || 'http://localhost';
|
return kc.redirectUri || 'http://localhost';
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login: function(options) {
|
login: function(options) {
|
||||||
var promise = createPromise();
|
var promise = createPromise();
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.TokenIdGenerator;
|
import org.keycloak.TokenIdGenerator;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
import org.keycloak.authentication.RequiredActionProvider;
|
import org.keycloak.authentication.RequiredActionProvider;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.connections.httpclient.HttpClientProvider;
|
import org.keycloak.connections.httpclient.HttpClientProvider;
|
||||||
|
@ -229,6 +230,10 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
|
|
||||||
String kcActionStatus = authSession.getClientNote(Constants.KC_ACTION_STATUS);
|
String kcActionStatus = authSession.getClientNote(Constants.KC_ACTION_STATUS);
|
||||||
if (kcActionStatus != null) {
|
if (kcActionStatus != null) {
|
||||||
|
String requiredActionAlias = authSession.getAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION);
|
||||||
|
if (requiredActionAlias != null) {
|
||||||
|
redirectUri.addParam(Constants.KC_ACTION, requiredActionAlias);
|
||||||
|
}
|
||||||
redirectUri.addParam(Constants.KC_ACTION_STATUS, kcActionStatus);
|
redirectUri.addParam(Constants.KC_ACTION_STATUS, kcActionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,17 @@ public abstract class AbstractAppInitiatedActionTest extends AbstractTestRealmKe
|
||||||
|
|
||||||
protected void assertKcActionStatus(String expectedStatus) {
|
protected void assertKcActionStatus(String expectedStatus) {
|
||||||
assertThat(appPage.getRequestType(),is(RequestType.AUTH_RESPONSE));
|
assertThat(appPage.getRequestType(),is(RequestType.AUTH_RESPONSE));
|
||||||
|
String kcActionStatus = getCurrentUrlParam(KC_ACTION_STATUS);
|
||||||
|
assertThat(kcActionStatus, is(expectedStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertKcAction(String expectedKcAction) {
|
||||||
|
assertThat(appPage.getRequestType(),is(RequestType.AUTH_RESPONSE));
|
||||||
|
String kcAction = getCurrentUrlParam(KC_ACTION);
|
||||||
|
assertThat(kcAction, is(expectedKcAction));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getCurrentUrlParam(String paramName) {
|
||||||
final URI url;
|
final URI url;
|
||||||
try {
|
try {
|
||||||
url = new URI(this.driver.getCurrentUrl());
|
url = new URI(this.driver.getCurrentUrl());
|
||||||
|
@ -88,14 +98,12 @@ public abstract class AbstractAppInitiatedActionTest extends AbstractTestRealmKe
|
||||||
}
|
}
|
||||||
|
|
||||||
List<NameValuePair> pairs = URLEncodedUtils.parse(url, StandardCharsets.UTF_8);
|
List<NameValuePair> pairs = URLEncodedUtils.parse(url, StandardCharsets.UTF_8);
|
||||||
String kcActionStatus = null;
|
|
||||||
for (NameValuePair p : pairs) {
|
for (NameValuePair p : pairs) {
|
||||||
if (p.getName().equals(KC_ACTION_STATUS)) {
|
if (p.getName().equals(paramName)) {
|
||||||
kcActionStatus = p.getValue();
|
return p.getValue();
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertThat(expectedStatus, is(kcActionStatus));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertSilentCancelMessage() {
|
protected void assertSilentCancelMessage() {
|
||||||
|
|
|
@ -79,6 +79,7 @@ public abstract class AbstractAppInitiatedActionUpdateEmailTest extends Abstract
|
||||||
emailUpdatePage.assertCurrent();
|
emailUpdatePage.assertCurrent();
|
||||||
emailUpdatePage.cancel();
|
emailUpdatePage.cancel();
|
||||||
|
|
||||||
|
assertKcAction(UserModel.RequiredAction.UPDATE_EMAIL.name());
|
||||||
assertKcActionStatus("cancelled");
|
assertKcActionStatus("cancelled");
|
||||||
|
|
||||||
// assert nothing was updated in persistent store
|
// assert nothing was updated in persistent store
|
||||||
|
|
Loading…
Reference in a new issue