From dddc5181e713077a9dfb30f60d0483870e292f1b Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Tue, 16 Jun 2015 22:48:39 -0400 Subject: [PATCH] required actions storage/display --- .../META-INF/jpa-changelog-1.4.0.xml | 1 + .../admin/resources/js/controllers/realm.js | 24 +++- .../admin/resources/js/controllers/users.js | 4 +- .../theme/base/admin/resources/js/loaders.js | 8 ++ .../theme/base/admin/resources/js/services.js | 9 +- .../partials/authentication-flows.html | 2 +- .../resources/partials/required-actions.html | 19 +++ .../admin/resources/partials/user-detail.html | 2 +- .../migration/migrators/MigrateTo1_3_0.java | 5 +- .../migration/migrators/MigrateTo1_4_0.java | 2 + .../java/org/keycloak/models/RealmModel.java | 1 + .../models/RequiredActionProviderModel.java | 15 +++ .../RequiredActionProviderEntity.java | 9 ++ .../models/utils/DefaultRequiredActions.java | 69 ++++++++++ .../models/file/adapter/RealmAdapter.java | 12 ++ .../keycloak/models/cache/RealmAdapter.java | 6 + .../models/cache/entities/CachedRealm.java | 6 + .../org/keycloak/models/jpa/RealmAdapter.java | 11 ++ .../RequiredActionProviderEntity.java | 11 ++ .../mongo/keycloak/adapters/RealmAdapter.java | 9 ++ .../managers/AuthenticationManager.java | 9 +- .../services/managers/RealmManager.java | 7 + .../AuthenticationManagementResource.java | 121 ++++++++++++++++++ .../resources/admin/RealmAdminResource.java | 14 -- 24 files changed, 345 insertions(+), 31 deletions(-) create mode 100755 model/api/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml index 460eac1599..fa92731a6b 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml @@ -77,6 +77,7 @@ + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index f2357009cc..61859e7065 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -1611,13 +1611,28 @@ module.controller('AuthenticationFlowsCtrl', function($scope, realm, Authenticat }); module.controller('RequiredActionsCtrl', function($scope, realm, RequiredActions, Notifications, Dialog, $location) { + console.log('RequiredActionsCtrl'); $scope.realm = realm; + $scope.requiredActions = []; + var setupRequiredActionsForm = function() { + console.log('setupRequiredActionsForm'); + RequiredActions.query({id: realm.realm}, function(data) { + $scope.requiredActions = []; + for (var i = 0; i < data.length; i++) { + $scope.requiredActions.push(data[i]); + } + }); + }; + $scope.updateRequiredAction = function(action) { + RequiredActions.update({realm: realm.realm, alias: action.alias}, action, function() { + Notifications.success("Auth requirement updated"); + setupForm(); + setupRequiredActionsForm(); + }); + } -}); - -module.controller('DefaultRequiredActionsCtrl', function($scope, realm, RequiredActions, Notifications, Dialog, $location) { - $scope.realm = realm; + setupRequiredActionsForm(); }); @@ -1627,3 +1642,4 @@ module.controller('DefaultRequiredActionsCtrl', function($scope, realm, Required + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index a2573cbd78..09b0fb940e 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -228,8 +228,8 @@ module.controller('UserDetailCtrl', function($scope, realm, user, User, UserFede RequiredActions.query({id: realm.realm}, function(data) { $scope.userReqActionList = []; for (var i = 0; i < data.length; i++) { - console.log("listed required action: " + data[i].text); - item = { id: data[i].id, text: data[i].text }; + console.log("listed required action: " + data[i].name); + item = data[i]; $scope.userReqActionList.push(item); } diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js index 3a492bbf53..5ebc0a53f6 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/loaders.js @@ -63,6 +63,14 @@ module.factory('UserListLoader', function(Loader, User, $route, $q) { }); }); +module.factory('RequiredActionsListLoader', function(Loader, RequiredActions, $route, $q) { + return Loader.query(RequiredActions, function() { + return { + realm : $route.current.params.realm + } + }); +}); + module.factory('RealmSessionStatsLoader', function(Loader, RealmSessionStats, $route, $q) { return Loader.get(RealmSessionStats, function() { return { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js index fa3115881f..8b641c1b3c 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -187,8 +187,13 @@ module.factory('RealmAdminEvents', function($resource) { }); module.factory('RequiredActions', function($resource) { - return $resource(authUrl + '/admin/realms/:id/required-actions', { - id : '@realm' + return $resource(authUrl + '/admin/realms/:id/authentication/required-actions/:alias', { + realm : '@realm', + alias : '@alias' + }, { + update : { + method : 'PUT' + } }); }); diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html index 0c83cdf772..cfc4f89fe3 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html @@ -27,7 +27,7 @@ - +

{{execution.referenceType}}

diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html index bf891c6895..ab49b59464 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html @@ -2,6 +2,25 @@

Authentication

+ + + + + + + + + + + + + + + + + + +
Required ActionEnabledDefault Action
{{requiredAction.name}}{{requiredAction.enabled}}{{requiredAction.defaultAction}}
No required actions configured
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html index 09a3fc4f02..9c851eec4c 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html @@ -80,7 +80,7 @@
Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address. 'Update profile' requires user to enter in new personal information. 'Update password' requires user to enter in a new password. 'Configure TOTP' requires setup of a mobile password generator. diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java index 1b68528ee4..43209a80d9 100755 --- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java +++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_3_0.java @@ -10,6 +10,7 @@ import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.models.utils.DefaultRequiredActions; import java.util.List; import java.util.Map; @@ -28,10 +29,6 @@ public class MigrateTo1_3_0 { public void migrate(KeycloakSession session) { List realms = session.realms().getRealms(); for (RealmModel realm : realms) { - if (realm.getAuthenticationFlows().size() == 0) { - DefaultAuthenticationFlows.addFlows(realm); - } - migrateLDAPProviders(session, realm); } diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java index cc15d00e89..0b7e8f825e 100755 --- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java +++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java @@ -4,6 +4,7 @@ import org.keycloak.migration.ModelVersion; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.models.utils.DefaultRequiredActions; import java.util.List; @@ -20,6 +21,7 @@ public class MigrateTo1_4_0 { for (RealmModel realm : realms) { if (realm.getAuthenticationFlows().size() == 0) { DefaultAuthenticationFlows.addFlows(realm); + DefaultRequiredActions.addActions(realm); } } diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java index 48c2b16837..8aaec249d4 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -204,6 +204,7 @@ public interface RealmModel extends RoleContainerModel { void updateRequiredActionProvider(RequiredActionProviderModel model); void removeRequiredActionProvider(RequiredActionProviderModel model); RequiredActionProviderModel getRequiredActionProviderById(String id); + RequiredActionProviderModel getRequiredActionProviderByAlias(String alias); List getIdentityProviders(); IdentityProviderModel getIdentityProviderByAlias(String alias); diff --git a/model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java b/model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java index f7d304e58f..a32a23c0a8 100755 --- a/model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java +++ b/model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java @@ -11,6 +11,7 @@ public class RequiredActionProviderModel { private String id; private String alias; + private String name; private String providerId; private boolean enabled; private boolean defaultAction; @@ -33,6 +34,20 @@ public class RequiredActionProviderModel { this.alias = alias; } + /** + * Used for display purposes. Probably should clean this code up and make alias and name the same, but + * the old code references an Enum and the admin console creates a "friendly" name for each enum. + * + * @return + */ + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public boolean isEnabled() { return enabled; } diff --git a/model/api/src/main/java/org/keycloak/models/entities/RequiredActionProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RequiredActionProviderEntity.java index 3aff7a9cc9..30fcf3efb0 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/RequiredActionProviderEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RequiredActionProviderEntity.java @@ -9,6 +9,7 @@ import java.util.Map; public class RequiredActionProviderEntity { protected String id; protected String alias; + protected String name; protected String providerId; protected boolean enabled; protected boolean defaultAction; @@ -30,6 +31,14 @@ public class RequiredActionProviderEntity { this.alias = alias; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + public boolean isEnabled() { return enabled; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java new file mode 100755 index 0000000000..ab5468cf68 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultRequiredActions.java @@ -0,0 +1,69 @@ +package org.keycloak.models.utils; + +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.AuthenticationFlowModel; +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionProviderModel; +import org.keycloak.models.UserModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class DefaultRequiredActions { + public static void addActions(RealmModel realm) { + if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.VERIFY_EMAIL.name()) == null) { + RequiredActionProviderModel verifyEmail = new RequiredActionProviderModel(); + verifyEmail.setEnabled(true); + verifyEmail.setAlias(UserModel.RequiredAction.VERIFY_EMAIL.name()); + verifyEmail.setName("Verify Email"); + verifyEmail.setProviderId(UserModel.RequiredAction.VERIFY_EMAIL.name()); + verifyEmail.setDefaultAction(false); + realm.addRequiredActionProvider(verifyEmail); + + } + + if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.UPDATE_PROFILE.name()) == null) { + RequiredActionProviderModel updateProfile = new RequiredActionProviderModel(); + updateProfile.setEnabled(true); + updateProfile.setAlias(UserModel.RequiredAction.UPDATE_PROFILE.name()); + updateProfile.setName("Update Profile"); + updateProfile.setProviderId(UserModel.RequiredAction.UPDATE_PROFILE.name()); + updateProfile.setDefaultAction(false); + realm.addRequiredActionProvider(updateProfile); + } + + if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.CONFIGURE_TOTP.name()) == null) { + RequiredActionProviderModel totp = new RequiredActionProviderModel(); + totp.setEnabled(true); + totp.setAlias(UserModel.RequiredAction.CONFIGURE_TOTP.name()); + totp.setName("Configure Totp"); + totp.setProviderId(UserModel.RequiredAction.CONFIGURE_TOTP.name()); + totp.setDefaultAction(false); + realm.addRequiredActionProvider(totp); + } + + if (realm.getRequiredActionProviderByAlias(UserModel.RequiredAction.UPDATE_PASSWORD.name()) == null) { + RequiredActionProviderModel updatePassword = new RequiredActionProviderModel(); + updatePassword.setEnabled(true); + updatePassword.setAlias(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + updatePassword.setName("Update Password"); + updatePassword.setProviderId(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + updatePassword.setDefaultAction(false); + realm.addRequiredActionProvider(updatePassword); + } + + if (realm.getRequiredActionProviderByAlias("terms_and_conditions") == null) { + RequiredActionProviderModel termsAndConditions = new RequiredActionProviderModel(); + termsAndConditions.setEnabled(false); + termsAndConditions.setAlias("terms_and_conditions"); + termsAndConditions.setName("Terms and Conditions"); + termsAndConditions.setProviderId("terms_and_conditions"); + termsAndConditions.setDefaultAction(false); + realm.addRequiredActionProvider(termsAndConditions); + } + + + } +} diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java index 9e6f286bee..c5b49d0491 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java @@ -1449,6 +1449,7 @@ public class RealmAdapter implements RealmModel { RequiredActionProviderEntity auth = new RequiredActionProviderEntity(); auth.setId(KeycloakModelUtils.generateId()); auth.setAlias(model.getAlias()); + auth.setName(model.getName()); auth.setProviderId(model.getProviderId()); auth.setConfig(model.getConfig()); auth.setEnabled(model.isEnabled()); @@ -1477,6 +1478,7 @@ public class RealmAdapter implements RealmModel { model.setId(entity.getId()); model.setProviderId(entity.getProviderId()); model.setAlias(entity.getAlias()); + model.setName(entity.getName()); model.setEnabled(entity.isEnabled()); model.setDefaultAction(entity.isDefaultAction()); Map config = new HashMap<>(); @@ -1492,6 +1494,7 @@ public class RealmAdapter implements RealmModel { entity.setAlias(model.getAlias()); entity.setProviderId(model.getProviderId()); entity.setEnabled(model.isEnabled()); + entity.setName(model.getName()); entity.setDefaultAction(model.isDefaultAction()); if (entity.getConfig() == null) { entity.setConfig(model.getConfig()); @@ -1521,6 +1524,15 @@ public class RealmAdapter implements RealmModel { return entity; } + @Override + public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { + for (RequiredActionProviderModel action : getRequiredActionProviders()) { + if (action.getAlias().equals(alias)) return action; + } + return null; + } + + @Override diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java index f21d120966..cf1bcb1e7d 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java @@ -1161,4 +1161,10 @@ public class RealmAdapter implements RealmModel { if (updated != null) return updated.getRequiredActionProviderById(id); return cached.getRequiredActionProviders().get(id); } + + @Override + public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { + if (updated != null) return updated.getRequiredActionProviderByAlias(alias); + return cached.getRequiredActionProvidersByAlias().get(alias); + } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java index 331b0205f1..acaea88d77 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java @@ -83,6 +83,7 @@ public class CachedRealm { private Map authenticationFlows = new HashMap<>(); private Map authenticators = new HashMap<>(); private Map requiredActionProviders = new HashMap<>(); + private Map requiredActionProvidersByAlias = new HashMap<>(); private MultivaluedHashMap authenticationExecutions = new MultivaluedHashMap<>(); private Map executionsById = new HashMap<>(); @@ -204,6 +205,7 @@ public class CachedRealm { } for (RequiredActionProviderModel action : model.getRequiredActionProviders()) { requiredActionProviders.put(action.getId(), action); + requiredActionProvidersByAlias.put(action.getAlias(), action); } } @@ -447,4 +449,8 @@ public class CachedRealm { public Map getRequiredActionProviders() { return requiredActionProviders; } + + public Map getRequiredActionProvidersByAlias() { + return requiredActionProvidersByAlias; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index eb7eec321c..5c62dcdf42 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1732,6 +1732,7 @@ public class RealmAdapter implements RealmModel { RequiredActionProviderEntity auth = new RequiredActionProviderEntity(); auth.setId(KeycloakModelUtils.generateId()); auth.setAlias(model.getAlias()); + auth.setName(model.getName()); auth.setRealm(realm); auth.setProviderId(model.getProviderId()); auth.setConfig(model.getConfig()); @@ -1767,6 +1768,7 @@ public class RealmAdapter implements RealmModel { model.setAlias(entity.getAlias()); model.setEnabled(entity.isEnabled()); model.setDefaultAction(entity.isDefaultAction()); + model.setName(entity.getName()); Map config = new HashMap<>(); if (entity.getConfig() != null) config.putAll(entity.getConfig()); model.setConfig(config); @@ -1781,6 +1783,7 @@ public class RealmAdapter implements RealmModel { entity.setProviderId(model.getProviderId()); entity.setEnabled(model.isEnabled()); entity.setDefaultAction(model.isDefaultAction()); + entity.setName(model.getName()); if (entity.getConfig() == null) { entity.setConfig(model.getConfig()); } else { @@ -1799,4 +1802,12 @@ public class RealmAdapter implements RealmModel { } return actions; } + + @Override + public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { + for (RequiredActionProviderModel action : getRequiredActionProviders()) { + if (action.getAlias().equals(alias)) return action; + } + return null; + } } \ No newline at end of file diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java index 103f76b222..4c5ecddb0a 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java @@ -30,6 +30,9 @@ public class RequiredActionProviderEntity { @Column(name="ALIAS") protected String alias; + @Column(name="NAME") + protected String name; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "REALM_ID") protected RealmEntity realm; @@ -104,4 +107,12 @@ public class RequiredActionProviderEntity { public void setConfig(Map config) { this.config = config; } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index b9f1ec81d1..bf1627fe52 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1607,6 +1607,15 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return entity; } + @Override + public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { + for (RequiredActionProviderModel action : getRequiredActionProviders()) { + if (action.getAlias().equals(alias)) return action; + } + return null; + } + + diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 56aface28b..9695a4efd9 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -17,6 +17,7 @@ import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.login.LoginFormsProvider; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.UserConsentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; @@ -483,8 +484,9 @@ public class AuthenticationManager { }; // see if any required actions need triggering, i.e. an expired password - for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) { - RequiredActionProvider provider = ((RequiredActionFactory)factory).create(session); + for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) { + if (!model.isEnabled()) continue; + RequiredActionProvider provider = session.getProvider(RequiredActionProvider.class, model.getProviderId()); provider.evaluateTriggers(context); } @@ -495,7 +497,8 @@ public class AuthenticationManager { Set requiredActions = user.getRequiredActions(); for (String action : requiredActions) { - RequiredActionProvider actionProvider = session.getProvider(RequiredActionProvider.class, action); + RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(action); + RequiredActionProvider actionProvider = session.getProvider(RequiredActionProvider.class, model.getProviderId()); Response challenge = actionProvider.invokeRequiredAction(context); if (challenge != null) { return challenge; diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index 41f5ac6b49..99b893c0ba 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -17,6 +17,7 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionProvider; import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.models.utils.DefaultRequiredActions; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.RepresentationToModel; import org.keycloak.representations.idm.ClientRepresentation; @@ -88,6 +89,7 @@ public class RealmManager { setupBrokerService(realm); setupAdminConsole(realm); setupAuthenticationFlows(realm); + setupRequiredActions(realm); return realm; } @@ -96,6 +98,10 @@ public class RealmManager { if (realm.getAuthenticationFlows().size() == 0) DefaultAuthenticationFlows.addFlows(realm); } + protected void setupRequiredActions(RealmModel realm) { + if (realm.getRequiredActionProviders().size() == 0) DefaultRequiredActions.addActions(realm); + } + protected void setupAdminConsole(RealmModel realm) { ClientModel adminConsole = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID); if (adminConsole == null) adminConsole = new ClientManager(this).createClient(realm, Constants.ADMIN_CONSOLE_CLIENT_ID); @@ -261,6 +267,7 @@ public class RealmManager { RepresentationToModel.importRealm(session, rep, realm); setupAuthenticationFlows(realm); + setupRequiredActions(realm); // Refresh periodic sync tasks for configured federationProviders List federationProviders = realm.getUserFederationProviders(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 3380731add..ebab2ae3bb 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -6,13 +6,18 @@ import org.jboss.resteasy.spi.NotFoundException; import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.AuthenticatorFactory; import org.keycloak.authentication.AuthenticatorUtil; +import org.keycloak.authentication.RequiredActionFactory; +import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticatorModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionProviderModel; +import org.keycloak.provider.ProviderFactory; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; @@ -20,8 +25,10 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import static javax.ws.rs.core.Response.Status.NOT_FOUND; @@ -176,4 +183,118 @@ public class AuthenticationManagementResource { realm.updateAuthenticatorExecution(model); } } + + public static class RequiredActionProviderRepresentation { + private String alias; + private String name; + private boolean enabled; + private boolean defaultAction; + private Map config = new HashMap(); + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isDefaultAction() { + return defaultAction; + } + + public void setDefaultAction(boolean defaultAction) { + this.defaultAction = defaultAction; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + } + + + @Path("required-actions") + @GET + @Produces(MediaType.APPLICATION_JSON) + public List getRequiredActions() { + List list = new LinkedList<>(); + for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) { + RequiredActionProviderRepresentation rep = toRepresentation(model); + list.add(rep); + } + return list; + } + + public static RequiredActionProviderRepresentation toRepresentation(RequiredActionProviderModel model) { + RequiredActionProviderRepresentation rep = new RequiredActionProviderRepresentation(); + rep.setAlias(model.getAlias()); + rep.setName(model.getName()); + rep.setDefaultAction(model.isDefaultAction()); + rep.setEnabled(model.isEnabled()); + rep.setConfig(model.getConfig()); + return rep; + } + + @Path("required-actions/{alias}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public RequiredActionProviderRepresentation getRequiredAction(@PathParam("alias") String alias) { + RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); + if (model == null) { + throw new NotFoundException("Failed to find required action: " + alias); + } + return toRepresentation(model); + } + + + @Path("required-actions/{alias}") + @PUT + @Consumes(MediaType.APPLICATION_JSON) + public void updateRequiredAction(@PathParam("alias") String alias, RequiredActionProviderRepresentation rep) { + RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); + if (model == null) { + throw new NotFoundException("Failed to find required action: " + alias); + } + RequiredActionProviderModel update = new RequiredActionProviderModel(); + update.setId(update.getId()); + update.setName(rep.getName()); + update.setAlias(rep.getAlias()); + update.setProviderId(model.getProviderId()); + update.setDefaultAction(rep.isDefaultAction()); + update.setEnabled(rep.isEnabled()); + update.setConfig(rep.getConfig()); + realm.updateRequiredActionProvider(update); + } + + @Path("required-actions/{alias}") + @DELETE + public void updateRequiredAction(@PathParam("alias") String alias) { + RequiredActionProviderModel model = realm.getRequiredActionProviderByAlias(alias); + if (model == null) { + throw new NotFoundException("Failed to find required action: " + alias); + } + realm.removeRequiredActionProvider(model); + } + + } \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 1853698eb2..5280a97878 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -565,18 +565,4 @@ public class RealmAdminResource { return new IdentityProvidersResource(realm, session, this.auth, adminEvent); } - @Path("required-actions") - @GET - @Produces(MediaType.APPLICATION_JSON) - public List> getRequiredActions() { - List> list = new LinkedList<>(); - for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) { - RequiredActionFactory actionFactory = (RequiredActionFactory)factory; - Map data = new HashMap<>(); - data.put("id", actionFactory.getId()); - data.put("text", actionFactory.getDisplayText()); - list.add(data); - } - return list; - } }