KEYCLOAK-6313 Add required action's priority for customizing the execution order
This commit is contained in:
parent
b43392bac8
commit
7c0ca9aad2
22 changed files with 577 additions and 13 deletions
|
@ -31,6 +31,7 @@ public class RequiredActionProviderRepresentation {
|
||||||
private String providerId;
|
private String providerId;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean defaultAction;
|
private boolean defaultAction;
|
||||||
|
private int priority;
|
||||||
private Map<String, String> config = new HashMap<String, String>();
|
private Map<String, String> config = new HashMap<String, String>();
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,6 +81,14 @@ public class RequiredActionProviderRepresentation {
|
||||||
this.providerId = providerId;
|
this.providerId = providerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(int priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getConfig() {
|
public Map<String, String> getConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,14 @@ public interface AuthenticationManagementResource {
|
||||||
@DELETE
|
@DELETE
|
||||||
void removeRequiredAction(@PathParam("alias") String alias);
|
void removeRequiredAction(@PathParam("alias") String alias);
|
||||||
|
|
||||||
|
@Path("required-actions/{alias}/raise-priority")
|
||||||
|
@POST
|
||||||
|
void raiseRequiredActionPriority(@PathParam("alias") String alias);
|
||||||
|
|
||||||
|
@Path("required-actions/{alias}/lower-priority")
|
||||||
|
@POST
|
||||||
|
void lowerRequiredActionPriority(@PathParam("alias") String alias);
|
||||||
|
|
||||||
@Path("config-description/{providerId}")
|
@Path("config-description/{providerId}")
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
|
|
@ -1661,6 +1661,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
auth.setConfig(model.getConfig());
|
auth.setConfig(model.getConfig());
|
||||||
auth.setEnabled(model.isEnabled());
|
auth.setEnabled(model.isEnabled());
|
||||||
auth.setDefaultAction(model.isDefaultAction());
|
auth.setDefaultAction(model.isDefaultAction());
|
||||||
|
auth.setPriority(model.getPriority());
|
||||||
realm.getRequiredActionProviders().add(auth);
|
realm.getRequiredActionProviders().add(auth);
|
||||||
em.persist(auth);
|
em.persist(auth);
|
||||||
em.flush();
|
em.flush();
|
||||||
|
@ -1691,6 +1692,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
model.setAlias(entity.getAlias());
|
model.setAlias(entity.getAlias());
|
||||||
model.setEnabled(entity.isEnabled());
|
model.setEnabled(entity.isEnabled());
|
||||||
model.setDefaultAction(entity.isDefaultAction());
|
model.setDefaultAction(entity.isDefaultAction());
|
||||||
|
model.setPriority(entity.getPriority());
|
||||||
model.setName(entity.getName());
|
model.setName(entity.getName());
|
||||||
Map<String, String> config = new HashMap<>();
|
Map<String, String> config = new HashMap<>();
|
||||||
if (entity.getConfig() != null) config.putAll(entity.getConfig());
|
if (entity.getConfig() != null) config.putAll(entity.getConfig());
|
||||||
|
@ -1706,6 +1708,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
entity.setProviderId(model.getProviderId());
|
entity.setProviderId(model.getProviderId());
|
||||||
entity.setEnabled(model.isEnabled());
|
entity.setEnabled(model.isEnabled());
|
||||||
entity.setDefaultAction(model.isDefaultAction());
|
entity.setDefaultAction(model.isDefaultAction());
|
||||||
|
entity.setPriority(model.getPriority());
|
||||||
entity.setName(model.getName());
|
entity.setName(model.getName());
|
||||||
if (entity.getConfig() == null) {
|
if (entity.getConfig() == null) {
|
||||||
entity.setConfig(model.getConfig());
|
entity.setConfig(model.getConfig());
|
||||||
|
@ -1725,6 +1728,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
for (RequiredActionProviderEntity entity : entities) {
|
for (RequiredActionProviderEntity entity : entities) {
|
||||||
actions.add(entityToModel(entity));
|
actions.add(entityToModel(entity));
|
||||||
}
|
}
|
||||||
|
Collections.sort(actions, RequiredActionProviderModel.RequiredActionComparator.SINGLETON);
|
||||||
return Collections.unmodifiableList(actions);
|
return Collections.unmodifiableList(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,9 @@ public class RequiredActionProviderEntity {
|
||||||
@Column(name="DEFAULT_ACTION")
|
@Column(name="DEFAULT_ACTION")
|
||||||
protected boolean defaultAction;
|
protected boolean defaultAction;
|
||||||
|
|
||||||
|
@Column(name="PRIORITY")
|
||||||
|
protected int priority;
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@MapKeyColumn(name="NAME")
|
@MapKeyColumn(name="NAME")
|
||||||
@Column(name="VALUE")
|
@Column(name="VALUE")
|
||||||
|
@ -136,6 +139,14 @@ public class RequiredActionProviderEntity {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(int priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!--
|
||||||
|
~ * Copyright 2018 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.
|
||||||
|
-->
|
||||||
|
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
||||||
|
|
||||||
|
<changeSet author="wadahiro@gmail.com" id="4.2.0-KEYCLOAK-6313">
|
||||||
|
<addColumn tableName="REQUIRED_ACTION_PROVIDER">
|
||||||
|
<column name="PRIORITY" type="INT"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
|
|
||||||
|
</databaseChangeLog>
|
|
@ -57,4 +57,5 @@
|
||||||
<include file="META-INF/jpa-changelog-authz-4.0.0.CR1.xml"/>
|
<include file="META-INF/jpa-changelog-authz-4.0.0.CR1.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-authz-4.0.0.Beta3.xml"/>
|
<include file="META-INF/jpa-changelog-authz-4.0.0.Beta3.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-authz-4.2.0.Final.xml"/>
|
<include file="META-INF/jpa-changelog-authz-4.2.0.Final.xml"/>
|
||||||
|
<include file="META-INF/jpa-changelog-4.2.0.xml"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.keycloak.migration.migrators.MigrateTo3_4_0;
|
||||||
import org.keycloak.migration.migrators.MigrateTo3_4_1;
|
import org.keycloak.migration.migrators.MigrateTo3_4_1;
|
||||||
import org.keycloak.migration.migrators.MigrateTo3_4_2;
|
import org.keycloak.migration.migrators.MigrateTo3_4_2;
|
||||||
import org.keycloak.migration.migrators.MigrateTo4_0_0;
|
import org.keycloak.migration.migrators.MigrateTo4_0_0;
|
||||||
|
import org.keycloak.migration.migrators.MigrateTo4_2_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;
|
||||||
|
@ -72,7 +73,8 @@ public class MigrationModelManager {
|
||||||
new MigrateTo3_4_0(),
|
new MigrateTo3_4_0(),
|
||||||
new MigrateTo3_4_1(),
|
new MigrateTo3_4_1(),
|
||||||
new MigrateTo3_4_2(),
|
new MigrateTo3_4_2(),
|
||||||
new MigrateTo4_0_0()
|
new MigrateTo4_0_0(),
|
||||||
|
new MigrateTo4_2_0()
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void migrate(KeycloakSession session) {
|
public static void migrate(KeycloakSession session) {
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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 static java.util.Comparator.comparing;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.migration.ModelVersion;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:wadahiro@gmail.com">Hiroyuki Wada</a>
|
||||||
|
*/
|
||||||
|
public class MigrateTo4_2_0 implements Migration {
|
||||||
|
|
||||||
|
public static final ModelVersion VERSION = new ModelVersion("4.2.0");
|
||||||
|
|
||||||
|
private static final Logger LOG = Logger.getLogger(MigrateTo4_2_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 json) {
|
||||||
|
// Set default priority of required actions in alphabetical order
|
||||||
|
List<RequiredActionProviderModel> actions = realm.getRequiredActionProviders().stream()
|
||||||
|
.sorted(comparing(RequiredActionProviderModel::getName)).collect(Collectors.toList());
|
||||||
|
int priority = 10;
|
||||||
|
for (RequiredActionProviderModel model : actions) {
|
||||||
|
LOG.debugf("Setting priority '%d' for required action '%s' in realm '%s'", priority, model.getAlias(),
|
||||||
|
realm.getName());
|
||||||
|
model.setPriority(priority);
|
||||||
|
priority += 10;
|
||||||
|
|
||||||
|
// Save
|
||||||
|
realm.updateRequiredActionProvider(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ public class DefaultRequiredActions {
|
||||||
verifyEmail.setName("Verify Email");
|
verifyEmail.setName("Verify Email");
|
||||||
verifyEmail.setProviderId(UserModel.RequiredAction.VERIFY_EMAIL.name());
|
verifyEmail.setProviderId(UserModel.RequiredAction.VERIFY_EMAIL.name());
|
||||||
verifyEmail.setDefaultAction(false);
|
verifyEmail.setDefaultAction(false);
|
||||||
|
verifyEmail.setPriority(50);
|
||||||
realm.addRequiredActionProvider(verifyEmail);
|
realm.addRequiredActionProvider(verifyEmail);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,6 +46,7 @@ public class DefaultRequiredActions {
|
||||||
updateProfile.setName("Update Profile");
|
updateProfile.setName("Update Profile");
|
||||||
updateProfile.setProviderId(UserModel.RequiredAction.UPDATE_PROFILE.name());
|
updateProfile.setProviderId(UserModel.RequiredAction.UPDATE_PROFILE.name());
|
||||||
updateProfile.setDefaultAction(false);
|
updateProfile.setDefaultAction(false);
|
||||||
|
updateProfile.setPriority(40);
|
||||||
realm.addRequiredActionProvider(updateProfile);
|
realm.addRequiredActionProvider(updateProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +57,7 @@ public class DefaultRequiredActions {
|
||||||
totp.setName("Configure OTP");
|
totp.setName("Configure OTP");
|
||||||
totp.setProviderId(UserModel.RequiredAction.CONFIGURE_TOTP.name());
|
totp.setProviderId(UserModel.RequiredAction.CONFIGURE_TOTP.name());
|
||||||
totp.setDefaultAction(false);
|
totp.setDefaultAction(false);
|
||||||
|
totp.setPriority(10);
|
||||||
realm.addRequiredActionProvider(totp);
|
realm.addRequiredActionProvider(totp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +68,7 @@ public class DefaultRequiredActions {
|
||||||
updatePassword.setName("Update Password");
|
updatePassword.setName("Update Password");
|
||||||
updatePassword.setProviderId(UserModel.RequiredAction.UPDATE_PASSWORD.name());
|
updatePassword.setProviderId(UserModel.RequiredAction.UPDATE_PASSWORD.name());
|
||||||
updatePassword.setDefaultAction(false);
|
updatePassword.setDefaultAction(false);
|
||||||
|
updatePassword.setPriority(30);
|
||||||
realm.addRequiredActionProvider(updatePassword);
|
realm.addRequiredActionProvider(updatePassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +79,7 @@ public class DefaultRequiredActions {
|
||||||
termsAndConditions.setName("Terms and Conditions");
|
termsAndConditions.setName("Terms and Conditions");
|
||||||
termsAndConditions.setProviderId("terms_and_conditions");
|
termsAndConditions.setProviderId("terms_and_conditions");
|
||||||
termsAndConditions.setDefaultAction(false);
|
termsAndConditions.setDefaultAction(false);
|
||||||
|
termsAndConditions.setPriority(20);
|
||||||
realm.addRequiredActionProvider(termsAndConditions);
|
realm.addRequiredActionProvider(termsAndConditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1862,6 +1862,7 @@ public class RepresentationToModel {
|
||||||
public static RequiredActionProviderModel toModel(RequiredActionProviderRepresentation rep) {
|
public static RequiredActionProviderModel toModel(RequiredActionProviderRepresentation rep) {
|
||||||
RequiredActionProviderModel model = new RequiredActionProviderModel();
|
RequiredActionProviderModel model = new RequiredActionProviderModel();
|
||||||
model.setConfig(rep.getConfig());
|
model.setConfig(rep.getConfig());
|
||||||
|
model.setPriority(rep.getPriority());
|
||||||
model.setDefaultAction(rep.isDefaultAction());
|
model.setDefaultAction(rep.isDefaultAction());
|
||||||
model.setEnabled(rep.isEnabled());
|
model.setEnabled(rep.isEnabled());
|
||||||
model.setProviderId(rep.getProviderId());
|
model.setProviderId(rep.getProviderId());
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -27,12 +28,22 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class RequiredActionProviderModel implements Serializable {
|
public class RequiredActionProviderModel implements Serializable {
|
||||||
|
|
||||||
|
public static class RequiredActionComparator implements Comparator<RequiredActionProviderModel> {
|
||||||
|
public static final RequiredActionComparator SINGLETON = new RequiredActionComparator();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(RequiredActionProviderModel o1, RequiredActionProviderModel o2) {
|
||||||
|
return o1.priority - o2.priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
private String alias;
|
private String alias;
|
||||||
private String name;
|
private String name;
|
||||||
private String providerId;
|
private String providerId;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private boolean defaultAction;
|
private boolean defaultAction;
|
||||||
|
private int priority;
|
||||||
private Map<String, String> config = new HashMap<String, String>();
|
private Map<String, String> config = new HashMap<String, String>();
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,6 +101,14 @@ public class RequiredActionProviderModel implements Serializable {
|
||||||
this.providerId = providerId;
|
this.providerId = providerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPriority(int priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getConfig() {
|
public Map<String, String> getConfig() {
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
|
@ -994,16 +994,10 @@ public class AuthenticationManager {
|
||||||
protected static Response executionActions(KeycloakSession session, AuthenticationSessionModel authSession,
|
protected static Response executionActions(KeycloakSession session, AuthenticationSessionModel authSession,
|
||||||
HttpRequest request, EventBuilder event, RealmModel realm, UserModel user,
|
HttpRequest request, EventBuilder event, RealmModel realm, UserModel user,
|
||||||
Set<String> requiredActions) {
|
Set<String> requiredActions) {
|
||||||
for (String action : requiredActions) {
|
|
||||||
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action);
|
|
||||||
if (model == null) {
|
|
||||||
logger.warnv("Could not find configuration for Required Action {0}, did you forget to register it?", action);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!model.isEnabled()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
List<RequiredActionProviderModel> sortedRequiredActions = sortRequiredActionsByPriority(realm, requiredActions);
|
||||||
|
|
||||||
|
for (RequiredActionProviderModel model : sortedRequiredActions) {
|
||||||
RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
|
RequiredActionFactory factory = (RequiredActionFactory)session.getKeycloakSessionFactory().getProviderFactory(RequiredActionProvider.class, model.getProviderId());
|
||||||
if (factory == null) {
|
if (factory == null) {
|
||||||
throw new RuntimeException("Unable to find factory for Required Action: " + model.getProviderId() + " did you forget to declare it in a META-INF/services file?");
|
throw new RuntimeException("Unable to find factory for Required Action: " + model.getProviderId() + " did you forget to declare it in a META-INF/services file?");
|
||||||
|
@ -1044,6 +1038,23 @@ public class AuthenticationManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<RequiredActionProviderModel> sortRequiredActionsByPriority(RealmModel realm, Set<String> requiredActions) {
|
||||||
|
List<RequiredActionProviderModel> actions = new ArrayList<>();
|
||||||
|
for (String action : requiredActions) {
|
||||||
|
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action);
|
||||||
|
if (model == null) {
|
||||||
|
logger.warnv("Could not find configuration for Required Action {0}, did you forget to register it?", action);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!model.isEnabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
actions.add(model);
|
||||||
|
}
|
||||||
|
Collections.sort(actions, RequiredActionProviderModel.RequiredActionComparator.SINGLETON);
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
public static void evaluateRequiredActionTriggers(final KeycloakSession session, final AuthenticationSessionModel authSession, final ClientConnection clientConnection, final HttpRequest request, final UriInfo uriInfo, final EventBuilder event, final RealmModel realm, final UserModel user) {
|
public static void evaluateRequiredActionTriggers(final KeycloakSession session, final AuthenticationSessionModel authSession, final ClientConnection clientConnection, final HttpRequest request, final UriInfo uriInfo, final EventBuilder event, final RealmModel realm, final UserModel user) {
|
||||||
|
|
||||||
// see if any required actions need triggering, i.e. an expired password
|
// see if any required actions need triggering, i.e. an expired password
|
||||||
|
|
|
@ -72,6 +72,7 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
import static javax.ws.rs.core.Response.Status.NOT_FOUND;
|
||||||
|
|
||||||
|
@ -880,6 +881,7 @@ public class AuthenticationManagementResource {
|
||||||
requiredAction.setName(name);
|
requiredAction.setName(name);
|
||||||
requiredAction.setProviderId(providerId);
|
requiredAction.setProviderId(providerId);
|
||||||
requiredAction.setDefaultAction(false);
|
requiredAction.setDefaultAction(false);
|
||||||
|
requiredAction.setPriority(getNextRequiredActionPriority());
|
||||||
requiredAction.setEnabled(true);
|
requiredAction.setEnabled(true);
|
||||||
requiredAction = realm.addRequiredActionProvider(requiredAction);
|
requiredAction = realm.addRequiredActionProvider(requiredAction);
|
||||||
|
|
||||||
|
@ -887,7 +889,12 @@ public class AuthenticationManagementResource {
|
||||||
adminEvent.operation(OperationType.CREATE).resource(ResourceType.REQUIRED_ACTION).resourcePath(uriInfo).representation(data).success();
|
adminEvent.operation(OperationType.CREATE).resource(ResourceType.REQUIRED_ACTION).resourcePath(uriInfo).representation(data).success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int getNextRequiredActionPriority() {
|
||||||
|
List<RequiredActionProviderModel> actions = realm.getRequiredActionProviders();
|
||||||
|
return actions.isEmpty() ? 0 : actions.get(actions.size() - 1).getPriority() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get required actions
|
* Get required actions
|
||||||
*
|
*
|
||||||
|
@ -913,6 +920,7 @@ public class AuthenticationManagementResource {
|
||||||
rep.setAlias(model.getAlias());
|
rep.setAlias(model.getAlias());
|
||||||
rep.setName(model.getName());
|
rep.setName(model.getName());
|
||||||
rep.setDefaultAction(model.isDefaultAction());
|
rep.setDefaultAction(model.isDefaultAction());
|
||||||
|
rep.setPriority(model.getPriority());
|
||||||
rep.setEnabled(model.isEnabled());
|
rep.setEnabled(model.isEnabled());
|
||||||
rep.setConfig(model.getConfig());
|
rep.setConfig(model.getConfig());
|
||||||
return rep;
|
return rep;
|
||||||
|
@ -959,6 +967,7 @@ public class AuthenticationManagementResource {
|
||||||
update.setAlias(rep.getAlias());
|
update.setAlias(rep.getAlias());
|
||||||
update.setProviderId(model.getProviderId());
|
update.setProviderId(model.getProviderId());
|
||||||
update.setDefaultAction(rep.isDefaultAction());
|
update.setDefaultAction(rep.isDefaultAction());
|
||||||
|
update.setPriority(rep.getPriority());
|
||||||
update.setEnabled(rep.isEnabled());
|
update.setEnabled(rep.isEnabled());
|
||||||
update.setConfig(rep.getConfig());
|
update.setConfig(rep.getConfig());
|
||||||
realm.updateRequiredActionProvider(update);
|
realm.updateRequiredActionProvider(update);
|
||||||
|
@ -984,6 +993,74 @@ public class AuthenticationManagementResource {
|
||||||
adminEvent.operation(OperationType.DELETE).resource(ResourceType.REQUIRED_ACTION).resourcePath(uriInfo).success();
|
adminEvent.operation(OperationType.DELETE).resource(ResourceType.REQUIRED_ACTION).resourcePath(uriInfo).success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raise required action's priority
|
||||||
|
*
|
||||||
|
* @param alias Alias of required action
|
||||||
|
*/
|
||||||
|
@Path("required-actions/{alias}/raise-priority")
|
||||||
|
@POST
|
||||||
|
@NoCache
|
||||||
|
public void raiseRequiredActionPriority(@PathParam("alias") String alias) {
|
||||||
|
auth.realm().requireManageRealm();
|
||||||
|
|
||||||
|
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias);
|
||||||
|
if (model == null) {
|
||||||
|
throw new NotFoundException("Failed to find required action.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RequiredActionProviderModel> actions = realm.getRequiredActionProviders();
|
||||||
|
RequiredActionProviderModel previous = null;
|
||||||
|
for (RequiredActionProviderModel action : actions) {
|
||||||
|
if (action.getId().equals(model.getId())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
previous = action;
|
||||||
|
}
|
||||||
|
if (previous == null) return;
|
||||||
|
int tmp = previous.getPriority();
|
||||||
|
previous.setPriority(model.getPriority());
|
||||||
|
realm.updateRequiredActionProvider(previous);
|
||||||
|
model.setPriority(tmp);
|
||||||
|
realm.updateRequiredActionProvider(model);
|
||||||
|
|
||||||
|
adminEvent.operation(OperationType.UPDATE).resource(ResourceType.REQUIRED_ACTION).resourcePath(uriInfo).success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lower required action's priority
|
||||||
|
*
|
||||||
|
* @param alias Alias of required action
|
||||||
|
*/
|
||||||
|
@Path("/required-actions/{alias}/lower-priority")
|
||||||
|
@POST
|
||||||
|
@NoCache
|
||||||
|
public void lowerRequiredActionPriority(@PathParam("alias") String alias) {
|
||||||
|
auth.realm().requireManageRealm();
|
||||||
|
|
||||||
|
RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias);
|
||||||
|
if (model == null) {
|
||||||
|
throw new NotFoundException("Failed to find required action.");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RequiredActionProviderModel> actions = realm.getRequiredActionProviders();
|
||||||
|
int i = 0;
|
||||||
|
for (i = 0; i < actions.size(); i++) {
|
||||||
|
if (actions.get(i).getId().equals(model.getId())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i + 1 >= actions.size()) return;
|
||||||
|
RequiredActionProviderModel next = actions.get(i + 1);
|
||||||
|
int tmp = model.getPriority();
|
||||||
|
model.setPriority(next.getPriority());
|
||||||
|
realm.updateRequiredActionProvider(model);
|
||||||
|
next.setPriority(tmp);
|
||||||
|
realm.updateRequiredActionProvider(next);
|
||||||
|
|
||||||
|
adminEvent.operation(OperationType.UPDATE).resource(ResourceType.REQUIRED_ACTION).resourcePath(uriInfo).success();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get authenticator provider's configuration description
|
* Get authenticator provider's configuration description
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -52,12 +53,15 @@ public class ActionUtil {
|
||||||
public static void addRequiredActionForRealm(RealmRepresentation testRealm, String providerId) {
|
public static void addRequiredActionForRealm(RealmRepresentation testRealm, String providerId) {
|
||||||
List<RequiredActionProviderRepresentation> requiredActions = testRealm.getRequiredActions();
|
List<RequiredActionProviderRepresentation> requiredActions = testRealm.getRequiredActions();
|
||||||
if (requiredActions == null) requiredActions = new LinkedList();
|
if (requiredActions == null) requiredActions = new LinkedList();
|
||||||
|
|
||||||
|
RequiredActionProviderRepresentation last = requiredActions.get(requiredActions.size() - 1);
|
||||||
|
|
||||||
RequiredActionProviderRepresentation action = new RequiredActionProviderRepresentation();
|
RequiredActionProviderRepresentation action = new RequiredActionProviderRepresentation();
|
||||||
action.setAlias(providerId);
|
action.setAlias(providerId);
|
||||||
action.setProviderId(providerId);
|
action.setProviderId(providerId);
|
||||||
action.setEnabled(true);
|
action.setEnabled(true);
|
||||||
action.setDefaultAction(true);
|
action.setDefaultAction(true);
|
||||||
|
action.setPriority(last.getPriority() + 1);
|
||||||
|
|
||||||
requiredActions.add(action);
|
requiredActions.add(action);
|
||||||
testRealm.setRequiredActions(requiredActions);
|
testRealm.setRequiredActions(requiredActions);
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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.testsuite.actions;
|
||||||
|
|
||||||
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.authentication.requiredactions.TermsAndConditions;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage.RequestType;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
|
||||||
|
import org.keycloak.testsuite.pages.TermsAndConditionsPage;
|
||||||
|
import org.keycloak.testsuite.util.UserBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:wadahiro@gmail.com">Hiroyuki Wada</a>
|
||||||
|
*/
|
||||||
|
public class RequiredActionPriorityTest extends AbstractTestRealmKeycloakTest {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTestRealm(RealmRepresentation testRealm) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected AppPage appPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected LoginPasswordUpdatePage changePasswordPage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected LoginUpdateProfileEditUsernameAllowedPage updateProfilePage;
|
||||||
|
|
||||||
|
@Page
|
||||||
|
protected TermsAndConditionsPage termsPage;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setupRequiredActions() {
|
||||||
|
setRequiredActionEnabled("test", TermsAndConditions.PROVIDER_ID, true, false);
|
||||||
|
|
||||||
|
// Because of changing the password in test case, we need to re-create the user.
|
||||||
|
ApiUtil.removeUserByUsername(testRealm(), "test-user@localhost");
|
||||||
|
UserRepresentation user = UserBuilder.create().enabled(true).username("test-user@localhost")
|
||||||
|
.email("test-user@localhost").build();
|
||||||
|
String testUserId = ApiUtil.createUserAndResetPasswordWithAdminClient(testRealm(), user, "password");
|
||||||
|
|
||||||
|
setRequiredActionEnabled("test", testUserId, RequiredAction.UPDATE_PASSWORD.name(), true);
|
||||||
|
setRequiredActionEnabled("test", testUserId, RequiredAction.UPDATE_PROFILE.name(), true);
|
||||||
|
setRequiredActionEnabled("test", testUserId, TermsAndConditions.PROVIDER_ID, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeRequiredActionsWithDefaultPriority() throws Exception {
|
||||||
|
// Default priority is alphabetical order:
|
||||||
|
// TermsAndConditions -> UpdatePassword -> UpdateProfile
|
||||||
|
|
||||||
|
// Login
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
// First, accept terms
|
||||||
|
termsPage.assertCurrent();
|
||||||
|
termsPage.acceptTerms();
|
||||||
|
events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).removeDetail(Details.REDIRECT_URI)
|
||||||
|
.detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID).assertEvent();
|
||||||
|
|
||||||
|
// Second, change password
|
||||||
|
changePasswordPage.assertCurrent();
|
||||||
|
changePasswordPage.changePassword("new-password", "new-password");
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
||||||
|
|
||||||
|
// Finally, update profile
|
||||||
|
updateProfilePage.assertCurrent();
|
||||||
|
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||||
|
.detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
|
||||||
|
|
||||||
|
// Logined
|
||||||
|
appPage.assertCurrent();
|
||||||
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
events.expectLogin().assertEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void executeRequiredActionsWithCustomPriority() throws Exception {
|
||||||
|
// Default priority is alphabetical order:
|
||||||
|
// TermsAndConditions -> UpdatePassword -> UpdateProfile
|
||||||
|
|
||||||
|
// After Changing the priority, the order will be:
|
||||||
|
// UpdatePassword -> UpdateProfile -> TermsAndConditions
|
||||||
|
testRealm().flows().raiseRequiredActionPriority(UserModel.RequiredAction.UPDATE_PASSWORD.name());
|
||||||
|
testRealm().flows().lowerRequiredActionPriority("terms_and_conditions");
|
||||||
|
|
||||||
|
// Login
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
|
// First, change password
|
||||||
|
changePasswordPage.assertCurrent();
|
||||||
|
changePasswordPage.changePassword("new-password", "new-password");
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PASSWORD).assertEvent();
|
||||||
|
|
||||||
|
// Second, update profile
|
||||||
|
updateProfilePage.assertCurrent();
|
||||||
|
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
|
||||||
|
.detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
|
||||||
|
events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
|
||||||
|
|
||||||
|
// Finally, accept terms
|
||||||
|
termsPage.assertCurrent();
|
||||||
|
termsPage.acceptTerms();
|
||||||
|
events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).removeDetail(Details.REDIRECT_URI)
|
||||||
|
.detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID).assertEvent();
|
||||||
|
|
||||||
|
// Logined
|
||||||
|
appPage.assertCurrent();
|
||||||
|
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
events.expectLogin().assertEvent();
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,6 +72,8 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCRUDRequiredAction() {
|
public void testCRUDRequiredAction() {
|
||||||
|
int lastPriority = authMgmtResource.getRequiredActions().get(authMgmtResource.getRequiredActions().size() - 1).getPriority();
|
||||||
|
|
||||||
// Just Dummy RequiredAction is not registered in the realm
|
// Just Dummy RequiredAction is not registered in the realm
|
||||||
List<RequiredActionProviderSimpleRepresentation> result = authMgmtResource.getUnregisteredRequiredActions();
|
List<RequiredActionProviderSimpleRepresentation> result = authMgmtResource.getUnregisteredRequiredActions();
|
||||||
Assert.assertEquals(1, result.size());
|
Assert.assertEquals(1, result.size());
|
||||||
|
@ -96,6 +98,9 @@ public class RequiredActionsTest extends AbstractAuthenticationTest {
|
||||||
compareRequiredAction(rep, newRequiredAction(DummyRequiredActionFactory.PROVIDER_ID, "Dummy Action",
|
compareRequiredAction(rep, newRequiredAction(DummyRequiredActionFactory.PROVIDER_ID, "Dummy Action",
|
||||||
true, false, Collections.<String, String>emptyMap()));
|
true, false, Collections.<String, String>emptyMap()));
|
||||||
|
|
||||||
|
// Confirm the registered priority - should be N + 1
|
||||||
|
Assert.assertEquals(lastPriority + 1, rep.getPriority());
|
||||||
|
|
||||||
// Update not-existent - should fail
|
// Update not-existent - should fail
|
||||||
try {
|
try {
|
||||||
authMgmtResource.updateRequiredAction("not-existent", rep);
|
authMgmtResource.updateRequiredAction("not-existent", rep);
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2018 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.testsuite.admin.authentication;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.ws.rs.NotFoundException;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.events.admin.ResourceType;
|
||||||
|
import org.keycloak.representations.idm.RequiredActionProviderRepresentation;
|
||||||
|
import org.keycloak.testsuite.util.AdminEventPaths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:wadahiro@gmail.com">Hiroyuki Wada</a>
|
||||||
|
*/
|
||||||
|
public class ShiftRequiredActionTest extends AbstractAuthenticationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testShiftRequiredAction() {
|
||||||
|
|
||||||
|
// get action
|
||||||
|
List<RequiredActionProviderRepresentation> actions = authMgmtResource.getRequiredActions();
|
||||||
|
|
||||||
|
RequiredActionProviderRepresentation last = actions.get(actions.size() - 1);
|
||||||
|
RequiredActionProviderRepresentation oneButLast = actions.get(actions.size() - 2);
|
||||||
|
|
||||||
|
// Not possible to raisePriority of not-existent required action
|
||||||
|
try {
|
||||||
|
authMgmtResource.raisePriority("not-existent");
|
||||||
|
Assert.fail("Not expected to raise priority of not existent required action");
|
||||||
|
} catch (NotFoundException nfe) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift last required action up
|
||||||
|
authMgmtResource.raiseRequiredActionPriority(last.getAlias());
|
||||||
|
assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authRaiseRequiredActionPath(last.getAlias()), ResourceType.REQUIRED_ACTION);
|
||||||
|
|
||||||
|
List<RequiredActionProviderRepresentation> actions2 = authMgmtResource.getRequiredActions();
|
||||||
|
|
||||||
|
RequiredActionProviderRepresentation last2 = actions2.get(actions.size() - 1);
|
||||||
|
RequiredActionProviderRepresentation oneButLast2 = actions2.get(actions.size() - 2);
|
||||||
|
|
||||||
|
Assert.assertEquals("Required action shifted up - N", last.getAlias(), oneButLast2.getAlias());
|
||||||
|
Assert.assertEquals("Required action up - N-1", oneButLast.getAlias(), last2.getAlias());
|
||||||
|
|
||||||
|
// Not possible to lowerPriority of not-existent required action
|
||||||
|
try {
|
||||||
|
authMgmtResource.lowerRequiredActionPriority("not-existent");
|
||||||
|
Assert.fail("Not expected to raise priority of not existent required action");
|
||||||
|
} catch (NotFoundException nfe) {
|
||||||
|
// Expected
|
||||||
|
}
|
||||||
|
|
||||||
|
// shift one before last down
|
||||||
|
authMgmtResource.lowerRequiredActionPriority(oneButLast2.getAlias());
|
||||||
|
assertAdminEvents.assertEvent(REALM_NAME, OperationType.UPDATE, AdminEventPaths.authLowerRequiredActionPath(oneButLast2.getAlias()), ResourceType.REQUIRED_ACTION);
|
||||||
|
|
||||||
|
actions2 = authMgmtResource.getRequiredActions();
|
||||||
|
|
||||||
|
last2 = actions2.get(actions.size() - 1);
|
||||||
|
oneButLast2 = actions2.get(actions.size() - 2);
|
||||||
|
|
||||||
|
Assert.assertEquals("Required action shifted down - N", last.getAlias(), last2.getAlias());
|
||||||
|
Assert.assertEquals("Required action shifted down - N-1", oneButLast.getAlias(), oneButLast2.getAlias());
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,6 +58,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
@ -198,6 +199,10 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
||||||
testOfflineScopeAddedToClient();
|
testOfflineScopeAddedToClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void testMigrationTo4_2_0() {
|
||||||
|
testRequiredActionsPriority(this.masterRealm, this.migrationRealm);
|
||||||
|
}
|
||||||
|
|
||||||
private void testCliConsoleScopeSize(RealmResource realm) {
|
private void testCliConsoleScopeSize(RealmResource realm) {
|
||||||
ClientRepresentation cli = realm.clients().findByClientId(Constants.ADMIN_CLI_CLIENT_ID).get(0);
|
ClientRepresentation cli = realm.clients().findByClientId(Constants.ADMIN_CLI_CLIENT_ID).get(0);
|
||||||
ClientRepresentation console = realm.clients().findByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID).get(0);
|
ClientRepresentation console = realm.clients().findByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID).get(0);
|
||||||
|
@ -462,6 +467,25 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void testRequiredActionsPriority(RealmResource... realms) {
|
||||||
|
log.info("testing required action's priority");
|
||||||
|
for (RealmResource realm : realms) {
|
||||||
|
List<RequiredActionProviderRepresentation> actions = realm.flows().getRequiredActions();
|
||||||
|
|
||||||
|
// Checking if the actions are in alphabetical order
|
||||||
|
List<String> nameList = actions.stream().map(x -> x.getName()).collect(Collectors.toList());
|
||||||
|
List<String> sortedByName = nameList.stream().sorted().collect(Collectors.toList());
|
||||||
|
assertArrayEquals(nameList.toArray(), sortedByName.toArray());
|
||||||
|
|
||||||
|
// Checking the priority
|
||||||
|
int priority = 10;
|
||||||
|
for (RequiredActionProviderRepresentation action : actions) {
|
||||||
|
assertEquals(priority, action.getPriority());
|
||||||
|
priority += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected String getMigrationMode() {
|
protected String getMigrationMode() {
|
||||||
return System.getProperty("migration.mode");
|
return System.getProperty("migration.mode");
|
||||||
}
|
}
|
||||||
|
@ -481,6 +505,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
protected void testMigrationTo4_x() {
|
protected void testMigrationTo4_x() {
|
||||||
testMigrationTo4_0_0();
|
testMigrationTo4_0_0();
|
||||||
|
testMigrationTo4_2_0();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -437,6 +437,18 @@ public class AdminEventPaths {
|
||||||
return uri.toString();
|
return uri.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String authRaiseRequiredActionPath(String requiredActionAlias) {
|
||||||
|
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "raiseRequiredActionPriority")
|
||||||
|
.build(requiredActionAlias);
|
||||||
|
return uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String authLowerRequiredActionPath(String requiredActionAlias) {
|
||||||
|
URI uri = UriBuilder.fromUri(authMgmtBasePath()).path(AuthenticationManagementResource.class, "lowerRequiredActionPriority")
|
||||||
|
.build(requiredActionAlias);
|
||||||
|
return uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
// ATTACK DETECTION
|
// ATTACK DETECTION
|
||||||
|
|
||||||
public static String attackDetectionClearBruteForceForUserPath(String username) {
|
public static String attackDetectionClearBruteForceForUserPath(String username) {
|
||||||
|
|
|
@ -2284,7 +2284,7 @@ module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flo
|
||||||
|
|
||||||
module.controller('RequiredActionsCtrl', function($scope, realm, unregisteredRequiredActions,
|
module.controller('RequiredActionsCtrl', function($scope, realm, unregisteredRequiredActions,
|
||||||
$modal, $route,
|
$modal, $route,
|
||||||
RegisterRequiredAction, RequiredActions, Notifications) {
|
RegisterRequiredAction, RequiredActions, RequiredActionRaisePriority, RequiredActionLowerPriority, Notifications) {
|
||||||
console.log('RequiredActionsCtrl');
|
console.log('RequiredActionsCtrl');
|
||||||
$scope.realm = realm;
|
$scope.realm = realm;
|
||||||
$scope.unregisteredRequiredActions = unregisteredRequiredActions;
|
$scope.unregisteredRequiredActions = unregisteredRequiredActions;
|
||||||
|
@ -2306,6 +2306,20 @@ module.controller('RequiredActionsCtrl', function($scope, realm, unregisteredReq
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.raisePriority = function(action) {
|
||||||
|
RequiredActionRaisePriority.save({realm: realm.realm, alias: action.alias}, function() {
|
||||||
|
Notifications.success("Required action's priority raised");
|
||||||
|
setupRequiredActionsForm();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.lowerPriority = function(action) {
|
||||||
|
RequiredActionLowerPriority.save({realm: realm.realm, alias: action.alias}, function() {
|
||||||
|
Notifications.success("Required action's priority lowered");
|
||||||
|
setupRequiredActionsForm();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
$scope.register = function() {
|
$scope.register = function() {
|
||||||
var controller = function($scope, $modalInstance) {
|
var controller = function($scope, $modalInstance) {
|
||||||
$scope.unregisteredRequiredActions = unregisteredRequiredActions;
|
$scope.unregisteredRequiredActions = unregisteredRequiredActions;
|
||||||
|
|
|
@ -323,6 +323,20 @@ module.factory('RequiredActions', function($resource) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.factory('RequiredActionRaisePriority', function($resource) {
|
||||||
|
return $resource(authUrl + '/admin/realms/:realm/authentication/required-actions/:alias/raise-priority', {
|
||||||
|
realm : '@realm',
|
||||||
|
alias : '@alias'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.factory('RequiredActionLowerPriority', function($resource) {
|
||||||
|
return $resource(authUrl + '/admin/realms/:realm/authentication/required-actions/:alias/lower-priority', {
|
||||||
|
realm : '@realm',
|
||||||
|
alias : '@alias'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
module.factory('UnregisteredRequiredActions', function($resource) {
|
module.factory('UnregisteredRequiredActions', function($resource) {
|
||||||
return $resource(authUrl + '/admin/realms/:realm/authentication/unregistered-required-actions', {
|
return $resource(authUrl + '/admin/realms/:realm/authentication/unregistered-required-actions', {
|
||||||
realm : '@realm'
|
realm : '@realm'
|
||||||
|
|
|
@ -18,8 +18,12 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="requiredAction in requiredActions | orderBy : 'name'" data-ng-show="requiredActions.length > 0">
|
<tr ng-repeat="requiredAction in requiredActions" data-ng-show="requiredActions.length > 0">
|
||||||
<td>{{requiredAction.name}}</td>
|
<td class="kc-sorter">
|
||||||
|
<button data-ng-hide="flow.builtIn" data-ng-disabled="$first" class="btn btn-default btn-sm" data-ng-click="raisePriority(requiredAction)"><i class="fa fa-angle-up"></i></button>
|
||||||
|
<button data-ng-hide="flow.builtIn" data-ng-disabled="$last" class="btn btn-default btn-sm" data-ng-click="lowerPriority(requiredAction)"><i class="fa fa-angle-down"></i></button>
|
||||||
|
<span>{{requiredAction.name}}</span></span>
|
||||||
|
</td>
|
||||||
<td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.enabled"></td>
|
<td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.enabled"></td>
|
||||||
<td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" ng-disabled="!requiredAction.enabled" ng-checked="requiredAction.enabled && requiredAction.defaultAction" id="{{requiredAction.alias}}.defaultAction"></td>
|
<td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" ng-disabled="!requiredAction.enabled" ng-checked="requiredAction.enabled && requiredAction.defaultAction" id="{{requiredAction.alias}}.defaultAction"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
Loading…
Reference in a new issue