KEYCLOAK-10854: App-initiated actions Phase I

This commit is contained in:
Stan Silvert 2019-07-25 18:24:33 -04:00 committed by Bruno Oliveira da Silva
parent 6c79bdee41
commit bc818367a1
25 changed files with 142 additions and 101 deletions

View file

@ -21,5 +21,5 @@ package org.keycloak.authentication;
* @author Stan Silvert * @author Stan Silvert
*/ */
public enum InitiatedActionSupport { public enum InitiatedActionSupport {
SUPPORTED, NOT_SUPPORTED, CONSENT_REQUIRED SUPPORTED, NOT_SUPPORTED
} }

View file

@ -41,7 +41,8 @@ public interface RequiredActionContext {
CHALLENGE, CHALLENGE,
SUCCESS, SUCCESS,
IGNORE, IGNORE,
FAILURE FAILURE,
CANCELED_AIA
} }
/** /**
@ -139,4 +140,10 @@ public interface RequiredActionContext {
*/ */
void ignore(); void ignore();
/**
* Mark application-initiated action as canceled by the user.
*
*/
void cancelAIA();
} }

View file

@ -17,7 +17,9 @@
package org.keycloak.authentication; package org.keycloak.authentication;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import org.keycloak.sessions.AuthenticationSessionModel;
/** /**
* RequiredAction provider. Required actions are one-time actions that a user must perform before they are logged in. * RequiredAction provider. Required actions are one-time actions that a user must perform before they are logged in.
@ -36,6 +38,18 @@ public interface RequiredActionProvider extends Provider {
return InitiatedActionSupport.NOT_SUPPORTED; return InitiatedActionSupport.NOT_SUPPORTED;
} }
/**
* Callback to let the action know that an application-initiated action
* was canceled.
*
* @param session The Keycloak session.
* @param authSession The authentication session.
*
*/
default void initiatedActionCanceled(KeycloakSession session, AuthenticationSessionModel authSession) {
return;
}
/** /**
* Called every time a user authenticates. This checks to see if this required action should be triggered. * Called every time a user authenticates. This checks to see if this required action should be triggered.
* The implementation of this method is responsible for setting the required action on the UserModel. * The implementation of this method is responsible for setting the required action on the UserModel.

View file

@ -45,7 +45,6 @@ import org.keycloak.migration.migrators.MigrateTo4_0_0;
import org.keycloak.migration.migrators.MigrateTo4_2_0; import org.keycloak.migration.migrators.MigrateTo4_2_0;
import org.keycloak.migration.migrators.MigrateTo4_6_0; import org.keycloak.migration.migrators.MigrateTo4_6_0;
import org.keycloak.migration.migrators.MigrateTo6_0_0; import org.keycloak.migration.migrators.MigrateTo6_0_0;
import org.keycloak.migration.migrators.MigrateTo7_0_0;
import org.keycloak.migration.migrators.Migration; import org.keycloak.migration.migrators.Migration;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -82,8 +81,7 @@ public class MigrationModelManager {
new MigrateTo4_0_0(), new MigrateTo4_0_0(),
new MigrateTo4_2_0(), new MigrateTo4_2_0(),
new MigrateTo4_6_0(), new MigrateTo4_6_0(),
new MigrateTo6_0_0(), new MigrateTo6_0_0()
new MigrateTo7_0_0()
}; };
public static void migrate(KeycloakSession session) { public static void migrate(KeycloakSession session) {

View file

@ -1,64 +0,0 @@
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.migration.migrators;
import org.jboss.logging.Logger;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.representations.idm.RealmRepresentation;
/**
* Implements the migration necessary for version 6.0.0.
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class MigrateTo7_0_0 implements Migration {
public static final ModelVersion VERSION = new ModelVersion("7.0.0");
private static final Logger LOG = Logger.getLogger(MigrateTo7_0_0.class);
@Override
public ModelVersion getVersion() {
return VERSION;
}
@Override
public void migrate(KeycloakSession session) {
session.realms().getRealms().stream().forEach(r -> {
migrateRealm(session, r, false);
});
}
@Override
public void migrateImport(KeycloakSession session, RealmModel realm, RealmRepresentation rep, boolean skipUserDependent) {
migrateRealm(session, realm, true);
}
protected void migrateRealm(KeycloakSession session, RealmModel realm, boolean jsn) {
ClientModel account = realm.getClientByClientId("account");
if (account != null) {
RoleModel role = account.addRole(AccountRoles.INITIATE_ACTION);
role.setDescription("${role_" + AccountRoles.INITIATE_ACTION + "}");
}
}
}

View file

@ -27,6 +27,6 @@ public interface AccountRoles {
String INITIATE_ACTION = "initiate-action"; String INITIATE_ACTION = "initiate-action";
String MANAGE_ACCOUNT_LINKS = "manage-account-links"; String MANAGE_ACCOUNT_LINKS = "manage-account-links";
String[] ALL = {VIEW_PROFILE, MANAGE_ACCOUNT, INITIATE_ACTION}; String[] ALL = {VIEW_PROFILE, MANAGE_ACCOUNT};
} }

View file

@ -63,6 +63,7 @@ public final class Constants {
public static final String KEY = "key"; public static final String KEY = "key";
public static final String KC_ACTION = "kc_action"; public static final String KC_ACTION = "kc_action";
public static final String IS_AIA_REQUEST = "IS_AIA_REQUEST"; public static final String IS_AIA_REQUEST = "IS_AIA_REQUEST";
public static final String AIA_SILENT_CANCEL = "silent_cancel";
public static final String SKIP_LINK = "skipLink"; public static final String SKIP_LINK = "skipLink";
public static final String TEMPLATE_ATTR_ACTION_URI = "actionUri"; public static final String TEMPLATE_ATTR_ACTION_URI = "actionUri";

View file

@ -43,6 +43,14 @@ public interface LoginProtocol extends Provider {
* Login cancelled by the user * Login cancelled by the user
*/ */
CANCELLED_BY_USER, CANCELLED_BY_USER,
/**
* Applications-initiated action was canceled by the user
*/
CANCELLED_AIA,
/**
* Applications-initiated action was canceled by the user. Do not send error.
*/
CANCELLED_AIA_SILENT,
/** /**
* Consent denied by the user * Consent denied by the user
*/ */

View file

@ -137,6 +137,11 @@ public class RequiredActionContextResult implements RequiredActionContext {
status = Status.IGNORE; status = Status.IGNORE;
} }
@Override
public void cancelAIA() {
status = Status.CANCELED_AIA;
}
@Override @Override
public URI getActionUrl(String code) { public URI getActionUrl(String code) {
ClientModel client = authenticationSession.getClient(); ClientModel client = authenticationSession.getClient();

View file

@ -269,9 +269,18 @@ public class OIDCLoginProtocol implements LoginProtocol {
String redirect = authSession.getRedirectUri(); String redirect = authSession.getRedirectUri();
String state = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM); String state = authSession.getClientNote(OIDCLoginProtocol.STATE_PARAM);
OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode).addParam(OAuth2Constants.ERROR, translateError(error)); OIDCRedirectUriBuilder redirectUri = OIDCRedirectUriBuilder.fromUri(redirect, responseMode);
if (state != null)
if (error != Error.CANCELLED_AIA_SILENT) {
redirectUri.addParam(OAuth2Constants.ERROR, translateError(error));
}
if (error == Error.CANCELLED_AIA) {
redirectUri.addParam(OAuth2Constants.ERROR_DESCRIPTION, "User cancelled aplication-initiated action.");
}
if (state != null) {
redirectUri.addParam(OAuth2Constants.STATE, state); redirectUri.addParam(OAuth2Constants.STATE, state);
}
new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true); new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, true);
return redirectUri.build(); return redirectUri.build();
} }
@ -279,6 +288,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
private String translateError(Error error) { private String translateError(Error error) {
switch (error) { switch (error) {
case CANCELLED_BY_USER: case CANCELLED_BY_USER:
case CANCELLED_AIA:
return OAuthErrorException.INTERACTION_REQUIRED;
case CONSENT_DENIED: case CONSENT_DENIED:
return OAuthErrorException.ACCESS_DENIED; return OAuthErrorException.ACCESS_DENIED;
case PASSIVE_INTERACTION_REQUIRED: case PASSIVE_INTERACTION_REQUIRED:

View file

@ -225,6 +225,7 @@ public class SamlProtocol implements LoginProtocol {
private JBossSAMLURIConstants translateErrorToSAMLStatus(Error error) { private JBossSAMLURIConstants translateErrorToSAMLStatus(Error error) {
switch (error) { switch (error) {
case CANCELLED_BY_USER: case CANCELLED_BY_USER:
case CANCELLED_AIA:
case CONSENT_DENIED: case CONSENT_DENIED:
return JBossSAMLURIConstants.STATUS_REQUEST_DENIED; return JBossSAMLURIConstants.STATUS_REQUEST_DENIED;
case PASSIVE_INTERACTION_REQUIRED: case PASSIVE_INTERACTION_REQUIRED:

View file

@ -45,7 +45,6 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ActionTokenKeyModel; import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.models.ActionTokenStoreProvider; import org.keycloak.models.ActionTokenStoreProvider;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
@ -66,6 +65,7 @@ import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocol.Error; import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
@ -96,6 +96,7 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.keycloak.models.AccountRoles;
import static org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint.LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX; import static org.keycloak.protocol.oidc.endpoints.AuthorizationEndpoint.LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX;
@ -132,6 +133,7 @@ public class AuthenticationManager {
private static final TokenTypeCheck VALIDATE_IDENTITY_COOKIE = new TokenTypeCheck(TokenUtil.TOKEN_TYPE_KEYCLOAK_ID); private static final TokenTypeCheck VALIDATE_IDENTITY_COOKIE = new TokenTypeCheck(TokenUtil.TOKEN_TYPE_KEYCLOAK_ID);
private static final String AIA_REQUEST = LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + Constants.KC_ACTION; private static final String AIA_REQUEST = LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + Constants.KC_ACTION;
public static final String IS_AIA_REQUEST = LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + Constants.IS_AIA_REQUEST; public static final String IS_AIA_REQUEST = LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + Constants.IS_AIA_REQUEST;
public static final String IS_SILENT_CANCEL = LOGIN_SESSION_NOTE_ADDITIONAL_REQ_PARAMS_PREFIX + Constants.AIA_SILENT_CANCEL;
public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) { public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
if (userSession == null) { if (userSession == null) {
@ -1144,6 +1146,11 @@ public class AuthenticationManager {
public void ignore() { public void ignore() {
throw new RuntimeException("Not allowed to call ignore() within evaluateTriggers()"); throw new RuntimeException("Not allowed to call ignore() within evaluateTriggers()");
} }
@Override
public void cancelAIA() {
throw new RuntimeException("Not allowed to call cancelAIA() within evaluateTriggers()");
}
}; };
evaluateApplicationInitiatedActionTrigger(session, provider, model, authSession); evaluateApplicationInitiatedActionTrigger(session, provider, model, authSession);
@ -1166,9 +1173,8 @@ public class AuthenticationManager {
// make sure you are evaluating the action that was requested // make sure you are evaluating the action that was requested
if (!aia.equalsIgnoreCase(model.getProviderId())) return; if (!aia.equalsIgnoreCase(model.getProviderId())) return;
if (session.getContext().getClient().getRole(AccountRoles.INITIATE_ACTION) == null) { if (session.getContext().getClient().getRole(AccountRoles.MANAGE_ACCOUNT) == null) {
logger.error("Client must have initiate-action role to perform application-initiated action."); throw new ForbiddenException("Client must have manage-account role to perform application-initiated actions.");
return;
} }
authSession.addRequiredAction(model.getProviderId()); authSession.addRequiredAction(model.getProviderId());

View file

@ -102,6 +102,7 @@ import java.util.Map;
import static org.keycloak.authentication.actiontoken.DefaultActionToken.ACTION_TOKEN_BASIC_CHECKS; import static org.keycloak.authentication.actiontoken.DefaultActionToken.ACTION_TOKEN_BASIC_CHECKS;
import static org.keycloak.services.managers.AuthenticationManager.IS_AIA_REQUEST; import static org.keycloak.services.managers.AuthenticationManager.IS_AIA_REQUEST;
import static org.keycloak.services.managers.AuthenticationManager.IS_SILENT_CANCEL;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -993,7 +994,8 @@ public class LoginActionsService {
Response response; Response response;
if (isCancelAppInitiatedAction(authSession, context)) { if (isCancelAppInitiatedAction(authSession, context)) {
context.failure(); provider.initiatedActionCanceled(session, authSession);
context.cancelAIA();
} else { } else {
provider.processAction(context); provider.processAction(context);
} }
@ -1014,15 +1016,11 @@ public class LoginActionsService {
} else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) { } else if (context.getStatus() == RequiredActionContext.Status.CHALLENGE) {
response = context.getChallenge(); response = context.getChallenge();
} else if (context.getStatus() == RequiredActionContext.Status.FAILURE) { } else if (context.getStatus() == RequiredActionContext.Status.FAILURE) {
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, authSession.getProtocol()); response = interruptionResponse(context, authSession, action, Error.CONSENT_DENIED);
protocol.setRealm(context.getRealm()) } else if (isSilentAIACancel(authSession, context)) {
.setHttpHeaders(context.getHttpRequest().getHttpHeaders()) response = interruptionResponse(context, authSession, action, Error.CANCELLED_AIA_SILENT);
.setUriInfo(context.getUriInfo()) } else if (context.getStatus() == RequiredActionContext.Status.CANCELED_AIA) {
.setEventBuilder(event); response = interruptionResponse(context, authSession, action, Error.CANCELLED_AIA);
event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
response = protocol.sendError(authSession, Error.CONSENT_DENIED);
event.error(Errors.REJECTED_BY_USER);
} else { } else {
throw new RuntimeException("Unreachable"); throw new RuntimeException("Unreachable");
} }
@ -1030,6 +1028,19 @@ public class LoginActionsService {
return BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, response, true, request); return BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, response, true, request);
} }
private Response interruptionResponse(RequiredActionContextResult context, AuthenticationSessionModel authSession, String action, Error error) {
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, authSession.getProtocol());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
.setUriInfo(context.getUriInfo())
.setEventBuilder(event);
event.detail(Details.CUSTOM_REQUIRED_ACTION, action);
event.error(Errors.REJECTED_BY_USER);
return protocol.sendError(authSession, error);
}
private boolean isCancelAppInitiatedAction(AuthenticationSessionModel authSession, RequiredActionContextResult context) { private boolean isCancelAppInitiatedAction(AuthenticationSessionModel authSession, RequiredActionContextResult context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters(); MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
@ -1039,4 +1050,12 @@ public class LoginActionsService {
return isAIARequest && userRequestedCancelAIA; return isAIARequest && userRequestedCancelAIA;
} }
private boolean isSilentAIACancel(AuthenticationSessionModel authSession, RequiredActionContextResult context) {
String silentCancel = authSession.getClientNote(IS_SILENT_CANCEL);
boolean isSilentCancel = "true".equalsIgnoreCase(silentCancel);
boolean isAIACancel = isCancelAppInitiatedAction(authSession, context);
return isSilentCancel && isAIACancel;
}
} }

View file

@ -50,8 +50,13 @@ public abstract class AbstractAppInitiatedActionTest extends AbstractTestRealmKe
} }
protected void doAIA() { protected void doAIA() {
doAIA(false);
}
protected void doAIA(boolean silentCancel) {
UriBuilder builder = OIDCLoginProtocolService.authUrl(authServerPage.createUriBuilder()); UriBuilder builder = OIDCLoginProtocolService.authUrl(authServerPage.createUriBuilder());
String uri = builder.queryParam("kc_action", this.aiaAction) String uri = builder.queryParam("kc_action", this.aiaAction)
.queryParam("silent_cancel", Boolean.toString(silentCancel))
.queryParam("response_type", "code") .queryParam("response_type", "code")
.queryParam("client_id", "test-app") .queryParam("client_id", "test-app")
.queryParam("scope", "openid") .queryParam("scope", "openid")
@ -64,4 +69,16 @@ public abstract class AbstractAppInitiatedActionTest extends AbstractTestRealmKe
protected void assertRedirectSuccess() { protected void assertRedirectSuccess() {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
} }
protected void assertCancelMessage() {
String url = this.driver.getCurrentUrl();
Assert.assertTrue("Expected 'error=interaction_required' in url", url.contains("error=interaction_required"));
Assert.assertTrue("Expected 'error_description=User+cancelled+aplication-initiated+action.' in url", url.contains("error_description=User+cancelled+aplication-initiated+action."));
}
protected void assertSilentCancelMessage() {
String url = this.driver.getCurrentUrl();
Assert.assertFalse("Expected no 'error=' in url", url.contains("error="));
Assert.assertFalse("Expected no 'error_description=' in url", url.contains("error_description="));
}
} }

View file

@ -50,6 +50,7 @@ public class AppInitiatedActionCancelTest extends AbstractAppInitiatedActionTest
updateProfilePage.assertCurrent(); updateProfilePage.assertCurrent();
updateProfilePage.cancel(); updateProfilePage.cancel();
assertRedirectSuccess(); assertRedirectSuccess();
assertCancelMessage();
appPage.logout(); appPage.logout();
@ -58,4 +59,14 @@ public class AppInitiatedActionCancelTest extends AbstractAppInitiatedActionTest
loginPage.login("test-user@localhost", "password"); loginPage.login("test-user@localhost", "password");
updateProfilePage.assertCurrent(); updateProfilePage.assertCurrent();
} }
@Test
public void silentCancelUpdateProfile() {
doAIA(true);
loginPage.login("test-user@localhost", "password");
updateProfilePage.assertCurrent();
updateProfilePage.cancel();
assertRedirectSuccess();
assertSilentCancelMessage();
}
} }

View file

@ -83,6 +83,7 @@ public class AppInitiatedActionResetPasswordTest extends AbstractAppInitiatedAct
changePasswordPage.cancel(); changePasswordPage.cancel();
assertRedirectSuccess(); assertRedirectSuccess();
assertCancelMessage();
} }
} }

View file

@ -17,10 +17,12 @@
package org.keycloak.testsuite.actions; package org.keycloak.testsuite.actions;
import java.util.List; import java.util.List;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Test; import org.junit.Test;
import org.keycloak.models.AccountRoles; import org.keycloak.models.AccountRoles;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testsuite.pages.ErrorPage;
/** /**
* *
@ -28,6 +30,9 @@ import org.keycloak.representations.idm.RoleRepresentation;
*/ */
public class AppInitiatedActionRoleTest extends AbstractAppInitiatedActionTest { public class AppInitiatedActionRoleTest extends AbstractAppInitiatedActionTest {
@Page
protected ErrorPage errorPage;
public AppInitiatedActionRoleTest() { public AppInitiatedActionRoleTest() {
super("update_profile"); super("update_profile");
} }
@ -36,15 +41,15 @@ public class AppInitiatedActionRoleTest extends AbstractAppInitiatedActionTest {
public void configureTestRealm(RealmRepresentation testRealm) { public void configureTestRealm(RealmRepresentation testRealm) {
List<RoleRepresentation> roleList = testRealm.getRoles().getClient().get("test-app"); List<RoleRepresentation> roleList = testRealm.getRoles().getClient().get("test-app");
RoleRepresentation initiateActionRole = null; RoleRepresentation manageAccountRole = null;
for (RoleRepresentation role : roleList) { for (RoleRepresentation role : roleList) {
if (role.getName().equals(AccountRoles.INITIATE_ACTION)) { if (role.getName().equals(AccountRoles.MANAGE_ACCOUNT)) {
initiateActionRole = role; manageAccountRole = role;
break; break;
} }
} }
roleList.remove(initiateActionRole); roleList.remove(manageAccountRole);
} }
@Test @Test
@ -52,6 +57,6 @@ public class AppInitiatedActionRoleTest extends AbstractAppInitiatedActionTest {
loginPage.open(); loginPage.open();
loginPage.login("test-user@localhost", "password"); loginPage.login("test-user@localhost", "password");
doAIA(); doAIA();
assertRedirectSuccess(); // update profile screen does not appear errorPage.assertCurrent();
} }
} }

View file

@ -127,6 +127,7 @@ public class AppInitiatedActionTotpSetupTest extends AbstractAppInitiatedActionT
totpPage.cancel(); totpPage.cancel();
assertRedirectSuccess(); assertRedirectSuccess();
assertCancelMessage();
} }
@Test @Test

View file

@ -133,6 +133,7 @@ public class AppInitiatedActionUpdateProfileTest extends AbstractAppInitiatedAct
updateProfilePage.cancel(); updateProfilePage.cancel();
assertRedirectSuccess(); assertRedirectSuccess();
assertCancelMessage();
// assert nothing was updated in persistent store // assert nothing was updated in persistent store
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost"); UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");

View file

@ -123,7 +123,7 @@ public class AddUserTest extends AbstractKeycloakTest {
} }
List<RoleRepresentation> accountRoles = userResource.roles().clientLevel(accountId).listAll(); List<RoleRepresentation> accountRoles = userResource.roles().clientLevel(accountId).listAll();
assertRoles(accountRoles, "initiate-action", "view-profile", "manage-account"); assertRoles(accountRoles, "view-profile", "manage-account");
} finally { } finally {
userResource.remove(); userResource.remove();
} }

View file

@ -438,7 +438,7 @@ public class ClientTest extends AbstractAdminTest {
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAll(), AccountRoles.VIEW_PROFILE); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAll(), AccountRoles.VIEW_PROFILE);
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective(), AccountRoles.VIEW_PROFILE); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective(), AccountRoles.VIEW_PROFILE);
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAvailable(), AccountRoles.INITIATE_ACTION, AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAvailable(), AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS);
Assert.assertNames(scopesResource.getAll().getRealmMappings(), "role1"); Assert.assertNames(scopesResource.getAll().getRealmMappings(), "role1");
Assert.assertNames(scopesResource.getAll().getClientMappings().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getMappings(), AccountRoles.VIEW_PROFILE); Assert.assertNames(scopesResource.getAll().getClientMappings().get(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getMappings(), AccountRoles.VIEW_PROFILE);
@ -453,7 +453,7 @@ public class ClientTest extends AbstractAdminTest {
Assert.assertNames(scopesResource.realmLevel().listEffective()); Assert.assertNames(scopesResource.realmLevel().listEffective());
Assert.assertNames(scopesResource.realmLevel().listAvailable(), "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "role1", "role2"); Assert.assertNames(scopesResource.realmLevel().listAvailable(), "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION, "role1", "role2");
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAll()); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAll());
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAvailable(), AccountRoles.INITIATE_ACTION, AccountRoles.VIEW_PROFILE, AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listAvailable(), AccountRoles.VIEW_PROFILE, AccountRoles.MANAGE_ACCOUNT, AccountRoles.MANAGE_ACCOUNT_LINKS);
Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective()); Assert.assertNames(scopesResource.clientLevel(accountMgmtId).listEffective());
} }

View file

@ -1409,7 +1409,7 @@ public class UserTest extends AbstractAdminTest {
assertNames(all.getRealmMappings(), "realm-role", "realm-composite", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION); assertNames(all.getRealmMappings(), "realm-role", "realm-composite", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
assertEquals(2, all.getClientMappings().size()); assertEquals(2, all.getClientMappings().size());
assertNames(all.getClientMappings().get("myclient").getMappings(), "client-role", "client-composite"); assertNames(all.getClientMappings().get("myclient").getMappings(), "client-role", "client-composite");
assertNames(all.getClientMappings().get("account").getMappings(), "initiate-action", "manage-account", "view-profile"); assertNames(all.getClientMappings().get("account").getMappings(), "manage-account", "view-profile");
// Remove realm role // Remove realm role
RoleRepresentation realmRoleRep = realm.roles().get("realm-role").toRepresentation(); RoleRepresentation realmRoleRep = realm.roles().get("realm-role").toRepresentation();

View file

@ -129,7 +129,7 @@ public class KcAdmSessionTest extends AbstractAdmCliTest {
realmMappings = StreamSupport.stream(clientRoles.get("account").get("mappings").spliterator(), false) realmMappings = StreamSupport.stream(clientRoles.get("account").get("mappings").spliterator(), false)
.map(o -> o.get("name").asText()).sorted().collect(Collectors.toList()); .map(o -> o.get("name").asText()).sorted().collect(Collectors.toList());
Assert.assertEquals(Arrays.asList("initiate-action", "manage-account", "view-profile"), realmMappings); Assert.assertEquals(Arrays.asList("manage-account", "view-profile"), realmMappings);
realmMappings = StreamSupport.stream(clientRoles.get("realm-management").get("mappings").spliterator(), false) realmMappings = StreamSupport.stream(clientRoles.get("realm-management").get("mappings").spliterator(), false)
.map(o -> o.get("name").asText()).sorted().collect(Collectors.toList()); .map(o -> o.get("name").asText()).sorted().collect(Collectors.toList());

View file

@ -417,8 +417,8 @@
"client" : { "client" : {
"test-app" : [ "test-app" : [
{ {
"name" : "initiate-action", "name": "manage-account",
"description": "Allow application-initiated actions" "description": "Allows application-initiated actions."
}, },
{ {
"name": "customer-user", "name": "customer-user",

View file

@ -79,7 +79,6 @@ role_manage-clients=Manage clients
role_manage-events=Manage events role_manage-events=Manage events
role_view-profile=View profile role_view-profile=View profile
role_manage-account=Manage account role_manage-account=Manage account
role_initiate-action=Initiate action
role_manage-account-links=Manage account links role_manage-account-links=Manage account links
role_read-token=Read token role_read-token=Read token
role_offline-access=Offline access role_offline-access=Offline access