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 80f0e3672a..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 @@ -7,12 +7,6 @@ - - - - - - @@ -78,9 +72,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml index bb3891477a..08d51a90a5 100755 --- a/connections/jpa/src/main/resources/META-INF/persistence.xml +++ b/connections/jpa/src/main/resources/META-INF/persistence.xml @@ -28,6 +28,7 @@ org.keycloak.models.jpa.entities.AuthenticationFlowEntity org.keycloak.models.jpa.entities.AuthenticationExecutionEntity org.keycloak.models.jpa.entities.AuthenticatorEntity + org.keycloak.models.jpa.entities.RequiredActionProviderEntity org.keycloak.models.sessions.jpa.entities.ClientSessionEntity diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index 822e688ccc..f31a00981e 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -1045,7 +1045,7 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'ProtocolListCtrl' }) - .when('/realms/:realm/authentication', { + .when('/realms/:realm/authentication/flows', { templateUrl : resourceUrl + '/partials/authentication-flows.html', resolve : { realm : function(RealmLoader) { @@ -1054,7 +1054,15 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'AuthenticationFlowsCtrl' }) - + .when('/realms/:realm/authentication/required-actions', { + templateUrl : resourceUrl + '/partials/required-actions.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + } + }, + controller : 'RequiredActionsCtrl' + }) .when('/server-info', { templateUrl : resourceUrl + '/partials/server-info.html' }) @@ -1502,6 +1510,15 @@ module.directive('kcTabsRealm', function () { } }); +module.directive('kcTabsAuthentication', function () { + return { + scope: true, + restrict: 'E', + replace: true, + templateUrl: resourceUrl + '/templates/kc-tabs-authentication.html' + } +}); + module.directive('kcTabsUser', function () { return { scope: true, 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 a88ffd73ab..a9c75807cd 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 @@ -1610,6 +1610,34 @@ 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({id: realm.realm, alias: action.alias}, action, function() { + Notifications.success("Required action updated"); + setupRequiredActionsForm(); + }); + } + + setupRequiredActionsForm(); + + +}); + + + 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..2ff1273816 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,9 +228,11 @@ 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 }; - $scope.userReqActionList.push(item); + console.log("listed required action: " + data[i].name); + if (data[i].enabled) { + var 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 067bb2077d..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' + } }); }); @@ -1074,7 +1079,7 @@ module.factory('IdentityProviderMapper', function($resource) { }); module.factory('AuthenticationExecutions', function($resource) { - return $resource(authUrl + '/admin/realms/:realm/authentication-flows/flow/:alias/executions', { + return $resource(authUrl + '/admin/realms/:realm/authentication/flow/:alias/executions', { realm : '@realm', alias : '@alias' }, { 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 b3ecf710a4..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 @@ -1,5 +1,7 @@
-

Authentication Flows {{realm.realm|capitalize}}

+

Authentication

+ + @@ -25,7 +27,7 @@ - + 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 new file mode 100755 index 0000000000..bdde0c50c6 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html @@ -0,0 +1,27 @@ +
+

Authentication

+ + +

{{execution.referenceType}}

+ + + + + + + + + + + + + + + + + +
Required ActionEnabledDefault Action
{{requiredAction.name}}
No required actions configured
+ +
+ + \ No newline at end of file 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/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html index ba09d84c63..a9316470b2 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html @@ -16,7 +16,7 @@
  • Roles
  • Identity Providers
  • User Federation
  • -
  • Authentication
  • +
  • Authentication
  • diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html new file mode 100755 index 0000000000..6fba9f10a9 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModel.java b/model/api/src/main/java/org/keycloak/migration/MigrationModel.java index df24b3d009..cb08418238 100755 --- a/model/api/src/main/java/org/keycloak/migration/MigrationModel.java +++ b/model/api/src/main/java/org/keycloak/migration/MigrationModel.java @@ -11,7 +11,7 @@ public interface MigrationModel { /** * Must have the form of major.minor.micro as the version is parsed and numbers are compared */ - public static final String LATEST_VERSION = "1.3.0.Beta1"; + public static final String LATEST_VERSION = "1.4.0"; String getStoredVersion(); void setStoredVersion(String version); diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java index 722bc5e021..b7910037fb 100755 --- a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java +++ b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java @@ -2,6 +2,7 @@ package org.keycloak.migration; import org.jboss.logging.Logger; import org.keycloak.migration.migrators.MigrateTo1_3_0; +import org.keycloak.migration.migrators.MigrateTo1_4_0; import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1; import org.keycloak.models.KeycloakSession; @@ -33,6 +34,12 @@ public class MigrationModelManager { } new MigrateTo1_3_0().migrate(session); } + if (stored == null || stored.lessThan(MigrateTo1_4_0.VERSION)) { + if (stored != null) { + logger.debug("Migrating older model to 1.4.0 updates"); + } + new MigrateTo1_4_0().migrate(session); + } model.setStoredVersion(MigrationModel.LATEST_VERSION); } 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 new file mode 100755 index 0000000000..0b7e8f825e --- /dev/null +++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java @@ -0,0 +1,30 @@ +package org.keycloak.migration.migrators; + +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; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class MigrateTo1_4_0 { + public static final ModelVersion VERSION = new ModelVersion("1.4.0"); + + + public void migrate(KeycloakSession session) { + List realms = session.realms().getRealms(); + 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 6366325364..8aaec249d4 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -156,13 +156,6 @@ public interface RealmModel extends RoleContainerModel { void updateDefaultRoles(String[] defaultRoles); - Set getDefaultRequiredActions(); - - void addDefaultRequiredAction(String action); - void removeDefaultRequiredAction(String action); - - void setDefaultRequiredActions(Set action); - // Key is clientId Map getClientNameMap(); @@ -206,6 +199,13 @@ public interface RealmModel extends RoleContainerModel { void removeAuthenticator(AuthenticatorModel model); AuthenticatorModel getAuthenticatorById(String id); + List getRequiredActionProviders(); + RequiredActionProviderModel addRequiredActionProvider(RequiredActionProviderModel model); + void updateRequiredActionProvider(RequiredActionProviderModel model); + void removeRequiredActionProvider(RequiredActionProviderModel model); + RequiredActionProviderModel getRequiredActionProviderById(String id); + RequiredActionProviderModel getRequiredActionProviderByAlias(String alias); + List getIdentityProviders(); IdentityProviderModel getIdentityProviderByAlias(String alias); void addIdentityProvider(IdentityProviderModel identityProvider); diff --git a/model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java b/model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java new file mode 100755 index 0000000000..a32a23c0a8 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java @@ -0,0 +1,82 @@ +package org.keycloak.models; + +import java.util.HashMap; +import java.util.Map; + +/** +* @author Bill Burke +* @version $Revision: 1 $ +*/ +public class RequiredActionProviderModel { + + private String id; + private String alias; + private String name; + private String providerId; + private boolean enabled; + private boolean defaultAction; + private Map config = new HashMap(); + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + 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; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isDefaultAction() { + return defaultAction; + } + + public void setDefaultAction(boolean defaultAction) { + this.defaultAction = defaultAction; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } +} diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java index b7f258828d..0548d4958f 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -78,7 +78,7 @@ public class RealmEntity extends AbstractIdentifiableEntity { private List identityProviderMappers = new ArrayList(); private List authenticationFlows = new ArrayList<>(); private List authenticators = new ArrayList<>(); - private List defaultRequiredActions = new ArrayList<>(); + private List requiredActionProviders = new ArrayList<>(); public String getName() { @@ -504,12 +504,12 @@ public class RealmEntity extends AbstractIdentifiableEntity { this.authenticators = authenticators; } - public List getDefaultRequiredActions() { - return defaultRequiredActions; + public List getRequiredActionProviders() { + return requiredActionProviders; } - public void setDefaultRequiredActions(List defaultRequiredActions) { - this.defaultRequiredActions = defaultRequiredActions; + public void setRequiredActionProviders(List requiredActionProviders) { + this.requiredActionProviders = requiredActionProviders; } } 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 new file mode 100755 index 0000000000..30fcf3efb0 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/entities/RequiredActionProviderEntity.java @@ -0,0 +1,73 @@ +package org.keycloak.models.entities; + +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class RequiredActionProviderEntity { + protected String id; + protected String alias; + protected String name; + protected String providerId; + protected boolean enabled; + protected boolean defaultAction; + private Map config; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + 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 boolean isDefaultAction() { + return defaultAction; + } + + public void setDefaultAction(boolean defaultAction) { + this.defaultAction = defaultAction; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } +} 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/FileUserProvider.java b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java index cf2f21dea6..eb28d309bd 100755 --- a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java +++ b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java @@ -25,6 +25,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; @@ -285,8 +286,10 @@ public class FileUserProvider implements UserProvider { } if (addDefaultRequiredActions) { - for (String r : realm.getDefaultRequiredActions()) { - userModel.addRequiredAction(r); + for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) { + if (r.isEnabled() && r.isDefaultAction()) { + userModel.addRequiredAction(r.getAlias()); + } } } 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 61cc52be78..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 @@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperEventImpl; @@ -41,6 +42,7 @@ import org.keycloak.models.entities.AuthenticatorEntity; import org.keycloak.models.entities.ClientEntity; import org.keycloak.models.entities.IdentityProviderMapperEntity; import org.keycloak.models.entities.RealmEntity; +import org.keycloak.models.entities.RequiredActionProviderEntity; import org.keycloak.models.entities.RequiredCredentialEntity; import org.keycloak.models.entities.RoleEntity; import org.keycloak.models.entities.UserFederationMapperEntity; @@ -1442,6 +1444,96 @@ public class RealmAdapter implements RealmModel { } } + @Override + public RequiredActionProviderModel addRequiredActionProvider(RequiredActionProviderModel model) { + 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()); + auth.setDefaultAction(model.isDefaultAction()); + realm.getRequiredActionProviders().add(auth); + model.setId(auth.getId()); + return model; + } + + @Override + public void removeRequiredActionProvider(RequiredActionProviderModel model) { + RequiredActionProviderEntity entity = getRequiredActionProviderEntity(model.getId()); + if (entity == null) return; + realm.getRequiredActionProviders().remove(entity); + } + + @Override + public RequiredActionProviderModel getRequiredActionProviderById(String id) { + RequiredActionProviderEntity entity = getRequiredActionProviderEntity(id); + if (entity == null) return null; + return entityToModel(entity); + } + + public RequiredActionProviderModel entityToModel(RequiredActionProviderEntity entity) { + RequiredActionProviderModel model = new RequiredActionProviderModel(); + 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<>(); + if (entity.getConfig() != null) config.putAll(entity.getConfig()); + model.setConfig(config); + return model; + } + + @Override + public void updateRequiredActionProvider(RequiredActionProviderModel model) { + RequiredActionProviderEntity entity = getRequiredActionProviderEntity(model.getId()); + if (entity == null) return; + 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()); + } else { + entity.getConfig().clear(); + entity.getConfig().putAll(model.getConfig()); + } + } + + @Override + public List getRequiredActionProviders() { + List actions = new LinkedList<>(); + for (RequiredActionProviderEntity entity : realm.getRequiredActionProviders()) { + actions.add(entityToModel(entity)); + } + return actions; + } + + public RequiredActionProviderEntity getRequiredActionProviderEntity(String id) { + RequiredActionProviderEntity entity = null; + for (RequiredActionProviderEntity auth : realm.getRequiredActionProviders()) { + if (auth.getId().equals(id)) { + entity = auth; + break; + } + } + return entity; + } + + @Override + public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { + for (RequiredActionProviderModel action : getRequiredActionProviders()) { + if (action.getAlias().equals(alias)) return action; + } + return null; + } + + + @Override public Set getUserFederationMappers() { @@ -1563,36 +1655,4 @@ public class RealmAdapter implements RealmModel { return mapper; } - @Override - public Set getDefaultRequiredActions() { - Set result = new HashSet(); - if (realm.getDefaultRequiredActions() != null) { - result.addAll(realm.getDefaultRequiredActions()); - } - return result; - } - - @Override - public void addDefaultRequiredAction(String action) { - Set actions = getDefaultRequiredActions(); - actions.add(action); - setDefaultRequiredActions(actions); - - } - - @Override - public void removeDefaultRequiredAction(String action) { - Set actions = getDefaultRequiredActions(); - actions.remove(action); - setDefaultRequiredActions(actions); - - } - - @Override - public void setDefaultRequiredActions(Set action) { - List result = new ArrayList(); - result.addAll(action); - realm.setDefaultRequiredActions(result); - - } -} + } 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 2db7ca2be9..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 @@ -10,6 +10,7 @@ import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperModel; @@ -1128,28 +1129,42 @@ public class RealmAdapter implements RealmModel { } @Override - public Set getDefaultRequiredActions() { - return cached.getDefaultRequiredActions(); + public List getRequiredActionProviders() { + if (updated != null) return updated.getRequiredActionProviders(); + List models = new ArrayList<>(); + models.addAll(cached.getRequiredActionProviders().values()); + return models; } @Override - public void addDefaultRequiredAction(String action) { + public RequiredActionProviderModel addRequiredActionProvider(RequiredActionProviderModel model) { getDelegateForUpdate(); - updated.addDefaultRequiredAction(action); + return updated.addRequiredActionProvider(model); + } + + @Override + public void updateRequiredActionProvider(RequiredActionProviderModel model) { + getDelegateForUpdate(); + updated.updateRequiredActionProvider(model); } @Override - public void removeDefaultRequiredAction(String action) { + public void removeRequiredActionProvider(RequiredActionProviderModel model) { getDelegateForUpdate(); - updated.removeDefaultRequiredAction(action); + updated.removeRequiredActionProvider(model); } @Override - public void setDefaultRequiredActions(Set action) { - getDelegateForUpdate(); - updated.setDefaultRequiredActions(action); + public RequiredActionProviderModel getRequiredActionProviderById(String id) { + 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 3ee785281d..b08748d9af 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 @@ -10,6 +10,7 @@ import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperModel; @@ -83,6 +84,8 @@ public class CachedRealm implements Serializable { private Map smtpConfig = new HashMap(); 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<>(); @@ -100,7 +103,6 @@ public class CachedRealm implements Serializable { private Set supportedLocales = new HashSet(); private String defaultLocale; private MultivaluedHashMap identityProviderMappers = new MultivaluedHashMap<>(); - private Set defaultRequiredActions = new HashSet<>(); public CachedRealm() { } @@ -203,7 +205,10 @@ public class CachedRealm implements Serializable { for (AuthenticatorModel authenticator : model.getAuthenticators()) { authenticators.put(authenticator.getId(), authenticator); } - this.defaultRequiredActions.addAll(model.getDefaultRequiredActions()); + for (RequiredActionProviderModel action : model.getRequiredActionProviders()) { + requiredActionProviders.put(action.getId(), action); + requiredActionProvidersByAlias.put(action.getAlias(), action); + } } @@ -443,7 +448,11 @@ public class CachedRealm implements Serializable { return executionsById; } - public Set getDefaultRequiredActions() { - return defaultRequiredActions; + public Map getRequiredActionProviders() { + return requiredActionProviders; + } + + public Map getRequiredActionProvidersByAlias() { + return requiredActionProvidersByAlias; } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 1c45bde919..ca30006d16 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -6,6 +6,7 @@ import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; @@ -68,9 +69,9 @@ public class JpaUserProvider implements UserProvider { } } } - if (addDefaultRequiredActions) { - for (String r : realm.getDefaultRequiredActions()) { - userModel.addRequiredAction(r); + for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) { + if (r.isEnabled() && r.isDefaultAction()) { + userModel.addRequiredAction(r.getAlias()); } } 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 33ea232574..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 @@ -11,6 +11,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperEventImpl; @@ -25,6 +26,7 @@ import org.keycloak.models.jpa.entities.IdentityProviderEntity; import org.keycloak.models.jpa.entities.IdentityProviderMapperEntity; import org.keycloak.models.jpa.entities.RealmAttributeEntity; import org.keycloak.models.jpa.entities.RealmEntity; +import org.keycloak.models.jpa.entities.RequiredActionProviderEntity; import org.keycloak.models.jpa.entities.RequiredCredentialEntity; import org.keycloak.models.jpa.entities.RoleEntity; import org.keycloak.models.jpa.entities.UserFederationMapperEntity; @@ -1726,29 +1728,86 @@ public class RealmAdapter implements RealmModel { } @Override - public Set getDefaultRequiredActions() { - Set result = new HashSet(); - result.addAll(realm.getDefaultRequiredActions()); - return result; - } - - - - @Override - public void setDefaultRequiredActions(Set actions) { - realm.setDefaultRequiredActions(actions); + public RequiredActionProviderModel addRequiredActionProvider(RequiredActionProviderModel model) { + 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()); + auth.setEnabled(model.isEnabled()); + auth.setDefaultAction(model.isDefaultAction()); + realm.getRequiredActionProviders().add(auth); + em.persist(auth); + em.flush(); + model.setId(auth.getId()); + return model; } @Override - public void addDefaultRequiredAction(String action) { - realm.getDefaultRequiredActions().add(action); + public void removeRequiredActionProvider(RequiredActionProviderModel model) { + RequiredActionProviderEntity entity = em.find(RequiredActionProviderEntity.class, model.getId()); + if (entity == null) return; + em.remove(entity); + em.flush(); + } @Override - public void removeDefaultRequiredAction(String action) { - realm.getDefaultRequiredActions().remove(action); + public RequiredActionProviderModel getRequiredActionProviderById(String id) { + RequiredActionProviderEntity entity = em.find(RequiredActionProviderEntity.class, id); + if (entity == null) return null; + return entityToModel(entity); } + public RequiredActionProviderModel entityToModel(RequiredActionProviderEntity entity) { + RequiredActionProviderModel model = new RequiredActionProviderModel(); + model.setId(entity.getId()); + model.setProviderId(entity.getProviderId()); + 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); + return model; + } + @Override + public void updateRequiredActionProvider(RequiredActionProviderModel model) { + RequiredActionProviderEntity entity = em.find(RequiredActionProviderEntity.class, model.getId()); + if (entity == null) return; + entity.setAlias(model.getAlias()); + 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 { + entity.getConfig().clear(); + entity.getConfig().putAll(model.getConfig()); + } + em.flush(); + } + + @Override + public List getRequiredActionProviders() { + List actions = new LinkedList<>(); + for (RequiredActionProviderEntity entity : realm.getRequiredActionProviders()) { + actions.add(entityToModel(entity)); + } + 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/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index 5ccb017fc9..be77599717 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java @@ -113,12 +113,6 @@ public class RealmEntity { @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") Collection roles = new ArrayList(); - @ElementCollection - @Column(name="VALUE") - @CollectionTable(name = "DEFAULT_REQUIRED_ACTIONS", joinColumns={ @JoinColumn(name="REALM_ID") }) - protected Set defaultRequiredActions = new HashSet(); - - @ElementCollection @MapKeyColumn(name="NAME") @Column(name="VALUE") @@ -163,6 +157,9 @@ public class RealmEntity { @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") Collection authenticators = new ArrayList<>(); + @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") + Collection requiredActionProviders = new ArrayList<>(); + @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") Collection authenticationFlows = new ArrayList<>(); @@ -567,6 +564,14 @@ public class RealmEntity { this.authenticators = authenticators; } + public Collection getRequiredActionProviders() { + return requiredActionProviders; + } + + public void setRequiredActionProviders(Collection requiredActionProviders) { + this.requiredActionProviders = requiredActionProviders; + } + public Collection getAuthenticationFlows() { return authenticationFlows; } @@ -575,12 +580,5 @@ public class RealmEntity { this.authenticationFlows = authenticationFlows; } - public Set getDefaultRequiredActions() { - return defaultRequiredActions; - } - - public void setDefaultRequiredActions(Set defaultRequiredActions) { - this.defaultRequiredActions = defaultRequiredActions; - } } 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 new file mode 100755 index 0000000000..4c5ecddb0a --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java @@ -0,0 +1,118 @@ +package org.keycloak.models.jpa.entities; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@Table(name="REQUIRED_ACTION_PROVIDER") +@Entity +@NamedQueries({ + @NamedQuery(name="deleteRequiredActionProviderByRealm", query="delete from RequiredActionProviderEntity action where action.realm = :realm"),}) +public class RequiredActionProviderEntity { + @Id + @Column(name="ID", length = 36) + protected String id; + + @Column(name="ALIAS") + protected String alias; + + @Column(name="NAME") + protected String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "REALM_ID") + protected RealmEntity realm; + + @Column(name="PROVIDER_ID") + protected String providerId; + + @Column(name="ENABLED") + protected boolean enabled; + + @Column(name="DEFAULT_ACTION") + protected boolean defaultAction; + + @ElementCollection + @MapKeyColumn(name="NAME") + @Column(name="VALUE") + @CollectionTable(name="REQUIRED_ACTION_CONFIG", joinColumns={ @JoinColumn(name="REQUIRED_ACTION_ID") }) + private Map config; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + 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 String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public RealmEntity getRealm() { + return realm; + } + + public void setRealm(RealmEntity realm) { + this.realm = realm; + } + + public Map getConfig() { + return config; + } + + 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/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java index abf8121a23..6c692de3c3 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java @@ -11,6 +11,7 @@ import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; @@ -258,8 +259,10 @@ public class MongoUserProvider implements UserProvider { } if (addDefaultRequiredActions) { - for (String r : realm.getDefaultRequiredActions()) { - userModel.addRequiredAction(r); + for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) { + if (r.isEnabled() && r.isDefaultAction()) { + userModel.addRequiredAction(r.getAlias()); + } } } 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 45456debde..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 @@ -16,6 +16,7 @@ import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; +import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperEventImpl; @@ -27,6 +28,7 @@ import org.keycloak.models.entities.AuthenticationFlowEntity; import org.keycloak.models.entities.AuthenticatorEntity; import org.keycloak.models.entities.IdentityProviderEntity; import org.keycloak.models.entities.IdentityProviderMapperEntity; +import org.keycloak.models.entities.RequiredActionProviderEntity; import org.keycloak.models.entities.RequiredCredentialEntity; import org.keycloak.models.entities.UserFederationMapperEntity; import org.keycloak.models.entities.UserFederationProviderEntity; @@ -1525,6 +1527,97 @@ public class RealmAdapter extends AbstractMongoAdapter impleme updateMongoEntity(); } + @Override + public RequiredActionProviderModel addRequiredActionProvider(RequiredActionProviderModel model) { + RequiredActionProviderEntity auth = new RequiredActionProviderEntity(); + auth.setId(KeycloakModelUtils.generateId()); + auth.setAlias(model.getAlias()); + auth.setProviderId(model.getProviderId()); + auth.setConfig(model.getConfig()); + auth.setEnabled(model.isEnabled()); + auth.setDefaultAction(model.isDefaultAction()); + realm.getRequiredActionProviders().add(auth); + model.setId(auth.getId()); + updateMongoEntity(); + return model; + } + + @Override + public void removeRequiredActionProvider(RequiredActionProviderModel model) { + RequiredActionProviderEntity entity = getRequiredActionProviderEntity(model.getId()); + if (entity == null) return; + getMongoEntity().getRequiredActionProviders().remove(entity); + updateMongoEntity(); + } + + @Override + public RequiredActionProviderModel getRequiredActionProviderById(String id) { + RequiredActionProviderEntity entity = getRequiredActionProviderEntity(id); + if (entity == null) return null; + return entityToModel(entity); + } + + public RequiredActionProviderModel entityToModel(RequiredActionProviderEntity entity) { + RequiredActionProviderModel model = new RequiredActionProviderModel(); + model.setId(entity.getId()); + model.setProviderId(entity.getProviderId()); + model.setAlias(entity.getAlias()); + model.setEnabled(entity.isEnabled()); + model.setDefaultAction(entity.isDefaultAction()); + Map config = new HashMap<>(); + if (entity.getConfig() != null) config.putAll(entity.getConfig()); + model.setConfig(config); + return model; + } + + @Override + public void updateRequiredActionProvider(RequiredActionProviderModel model) { + RequiredActionProviderEntity entity = getRequiredActionProviderEntity(model.getId()); + if (entity == null) return; + entity.setAlias(model.getAlias()); + entity.setProviderId(model.getProviderId()); + entity.setEnabled(model.isEnabled()); + entity.setDefaultAction(model.isDefaultAction()); + if (entity.getConfig() == null) { + entity.setConfig(model.getConfig()); + } else { + entity.getConfig().clear(); + entity.getConfig().putAll(model.getConfig()); + } + updateMongoEntity(); + } + + @Override + public List getRequiredActionProviders() { + List actions = new LinkedList<>(); + for (RequiredActionProviderEntity entity : realm.getRequiredActionProviders()) { + actions.add(entityToModel(entity)); + } + return actions; + } + + public RequiredActionProviderEntity getRequiredActionProviderEntity(String id) { + RequiredActionProviderEntity entity = null; + for (RequiredActionProviderEntity auth : getMongoEntity().getRequiredActionProviders()) { + if (auth.getId().equals(id)) { + entity = auth; + break; + } + } + return entity; + } + + @Override + public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { + for (RequiredActionProviderModel action : getRequiredActionProviders()) { + if (action.getAlias().equals(alias)) return action; + } + return null; + } + + + + @Override public Set getUserFederationMappers() { @@ -1648,32 +1741,4 @@ public class RealmAdapter extends AbstractMongoAdapter impleme mapper.setConfig(config); return mapper; } - - @Override - public Set getDefaultRequiredActions() { - Set result = new HashSet(); - result.addAll(realm.getDefaultRequiredActions()); - return result; - } - - - - @Override - public void setDefaultRequiredActions(Set actions) { - List result = new ArrayList(); - result.addAll(actions); - getMongoEntity().setDefaultRequiredActions(result); - updateMongoEntity(); - } - - @Override - public void addDefaultRequiredAction(String action) { - getMongoStore().pushItemToList(getMongoEntity(), "defaultRequiredActions", action, true, invocationContext); - } - - @Override - public void removeDefaultRequiredAction(String action) { - getMongoStore().pullItemFromList(getMongoEntity(), "defaultRequiredActions", action, invocationContext); - } - } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index af6a67166d..fcc3a76892 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -19,6 +19,7 @@ import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.services.ErrorPage; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.BruteForceProtector; +import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import javax.ws.rs.core.Response; @@ -62,6 +63,7 @@ public class AuthenticationProcessor { } public static enum Error { + EXPIRED_CODE, INVALID_CLIENT_SESSION, INVALID_USER, INVALID_CREDENTIALS, @@ -317,6 +319,13 @@ public class AuthenticationProcessor { public String getForwardedErrorMessage() { return AuthenticationProcessor.this.forwardedErrorMessage; } + + @Override + public String generateAccessCode() { + ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession()); + accessCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); + return accessCode.getCode(); + } } public static class AuthException extends RuntimeException { @@ -388,6 +397,10 @@ public class AuthenticationProcessor { event.error(Errors.INVALID_CODE); return ErrorPage.error(session, Messages.INVALID_CODE); + } else if (e.getError() == Error.EXPIRED_CODE) { + event.error(Errors.EXPIRED_CODE); + return ErrorPage.error(session, Messages.EXPIRED_CODE); + }else { event.error(Errors.INVALID_USER_CREDENTIALS); return ErrorPage.error(session, Messages.INVALID_USER); @@ -403,9 +416,7 @@ public class AuthenticationProcessor { public Response authenticate() throws AuthException { - if (!ClientSessionModel.Action.AUTHENTICATE.name().equals(clientSession.getAction())) { - throw new AuthException(Error.INVALID_CLIENT_SESSION); - } + checkClientSession(); logger.debug("AUTHENTICATE"); event.event(EventType.LOGIN); event.client(clientSession.getClient().getClientId()) @@ -425,10 +436,18 @@ public class AuthenticationProcessor { return authenticationComplete(); } - public Response authenticateOnly() throws AuthException { - if (!ClientSessionModel.Action.AUTHENTICATE.name().equals(clientSession.getAction())) { + public void checkClientSession() { + ClientSessionCode code = new ClientSessionCode(realm, clientSession); + if (!code.isValidAction(ClientSessionModel.Action.AUTHENTICATE.name())) { throw new AuthException(Error.INVALID_CLIENT_SESSION); } + if (!code.isActionActive(ClientSessionModel.Action.AUTHENTICATE.name())) { + throw new AuthException(Error.EXPIRED_CODE); + } + } + + public Response authenticateOnly() throws AuthException { + checkClientSession(); event.event(EventType.LOGIN); event.client(clientSession.getClient().getClientId()) .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) @@ -518,10 +537,7 @@ public class AuthenticationProcessor { if (model.isUserSetupAllowed()) { logger.debugv("authenticator SETUP_REQUIRED: {0}", authenticatorModel.getProviderId()); clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED); - String requiredAction = authenticator.getRequiredAction(); - if (!authUser.getRequiredActions().contains(requiredAction)) { - authUser.addRequiredAction(requiredAction); - } + authenticator.setRequiredActions(session, realm, clientSession.getAuthenticatedUser()); continue; } else { throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED); diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java index ee9d43500b..8245f82fe7 100755 --- a/services/src/main/java/org/keycloak/authentication/Authenticator.java +++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java @@ -13,7 +13,12 @@ public interface Authenticator extends Provider { boolean requiresUser(); void authenticate(AuthenticatorContext context); boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user); - String getRequiredAction(); + + /** + * Set actions to configure authenticator + * + */ + void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user); } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java index a8f5aadc8f..637d3a8399 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java @@ -73,4 +73,11 @@ public interface AuthenticatorContext { * whatever form is challenging. */ String getForwardedErrorMessage(); + + /** + * Generates access code and updates clientsession timestamp + * + * @return + */ + String generateAccessCode(); } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java index 561e60de2c..a6ac452030 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java @@ -17,7 +17,7 @@ public interface AuthenticatorFactory extends ProviderFactory, Co String getDisplayType(); /** - * General authenticator type, i.e. totp, password, cert + * General authenticator type, i.e. totp, password, cert. * * @return null if not a referencable type */ @@ -26,8 +26,7 @@ public interface AuthenticatorFactory extends ProviderFactory, Co boolean isConfigurable(); /** - * What requirement settings are allowed. For example, KERBEROS can only be required because of the way its challenges - * work. + * What requirement settings are allowed. * * @return */ diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java index 44d11fdbf9..9184034e20 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java @@ -5,9 +5,7 @@ import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.AuthenticatorContext; import org.keycloak.events.Errors; import org.keycloak.login.LoginFormsProvider; -import org.keycloak.models.ClientSessionModel; import org.keycloak.models.UserModel; -import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import org.keycloak.services.resources.LoginActionsService; @@ -29,22 +27,21 @@ public class AbstractFormAuthenticator { } protected LoginFormsProvider loginForm(AuthenticatorContext context) { - ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession()); - code.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - URI action = getActionUrl(context, code, LOGIN_FORM_ACTION); + String accessCode = context.generateAccessCode(); + URI action = getActionUrl(context, accessCode, LOGIN_FORM_ACTION); LoginFormsProvider provider = context.getSession().getProvider(LoginFormsProvider.class) .setUser(context.getUser()) .setActionUri(action) - .setClientSessionCode(code.getCode()); + .setClientSessionCode(accessCode); if (context.getForwardedErrorMessage() != null) { provider.setError(context.getForwardedErrorMessage()); } return provider; } - public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code, String action) { + public static URI getActionUrl(AuthenticatorContext context, String code, String action) { return LoginActionsService.authenticationFormProcessor(context.getUriInfo()) - .queryParam(OAuth2Constants.CODE, code.getCode()) + .queryParam(OAuth2Constants.CODE, code) .queryParam(ACTION, action) .build(context.getRealm().getName()); } @@ -52,25 +49,21 @@ public class AbstractFormAuthenticator { protected Response invalidUser(AuthenticatorContext context) { return loginForm(context) .setError(Messages.INVALID_USER) - .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) .createLogin(); } protected Response disabledUser(AuthenticatorContext context) { return loginForm(context) - .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) .setError(Messages.ACCOUNT_DISABLED).createLogin(); } protected Response temporarilyDisabledUser(AuthenticatorContext context) { return loginForm(context) - .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin(); } protected Response invalidCredentials(AuthenticatorContext context) { return loginForm(context) - .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) .setError(Messages.INVALID_USER).createLogin(); } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java index 1455b2f6c9..a4d64301d5 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java @@ -38,8 +38,7 @@ public class CookieAuthenticator implements Authenticator { } @Override - public String getRequiredAction() { - return null; + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { } @Override diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java index bac37f7822..56ef93a96b 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java @@ -68,8 +68,11 @@ public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator { } @Override - public String getRequiredAction() { - return UserModel.RequiredAction.CONFIGURE_TOTP.name(); + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + if (!user.getRequiredActions().contains(UserModel.RequiredAction.CONFIGURE_TOTP.name())) { + user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP.name()); + } + } @Override diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java index fb393e2fe7..cdd49b95fd 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java @@ -70,8 +70,11 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat } @Override - public String getRequiredAction() { - return UserModel.RequiredAction.UPDATE_PASSWORD.name(); + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + if (!user.getRequiredActions().contains(UserModel.RequiredAction.UPDATE_PASSWORD.name())) { + user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + } + } @Override diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java index 62e964318d..6c3759a6c7 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java @@ -111,8 +111,7 @@ public class LoginFormUsernameAuthenticator extends AbstractFormAuthenticator im } @Override - public String getRequiredAction() { - return null; + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { } @Override diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java index 325f3cd77d..bce060b4da 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java @@ -11,7 +11,6 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import javax.ws.rs.core.MultivaluedMap; @@ -69,11 +68,11 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A } protected Response challenge(AuthenticatorContext context, String error) { - ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); - URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode, TOTP_FORM_ACTION); + String accessCode = context.generateAccessCode(); + URI action = AbstractFormAuthenticator.getActionUrl(context, accessCode, TOTP_FORM_ACTION); LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class) .setActionUri(action) - .setClientSessionCode(clientSessionCode.getCode()); + .setClientSessionCode(accessCode); if (error != null) forms.setError(error); return forms.createLoginTotp(); @@ -85,8 +84,11 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A } @Override - public String getRequiredAction() { - return UserModel.RequiredAction.CONFIGURE_TOTP.name(); + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { + if (!user.getRequiredActions().contains(UserModel.RequiredAction.CONFIGURE_TOTP.name())) { + user.addRequiredAction(UserModel.RequiredAction.CONFIGURE_TOTP.name()); + } + } @Override diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java index 3970fa8916..eb6c1eeea0 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java @@ -15,7 +15,6 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; -import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; import javax.ws.rs.core.HttpHeaders; @@ -131,9 +130,8 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au * @return */ protected Response optionalChallengeRedirect(AuthenticatorContext context, String negotiateHeader) { - ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession()); - code.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - URI action = getActionUrl(context, code, KERBEROS_DISABLED); + String accessCode = context.generateAccessCode(); + URI action = getActionUrl(context, accessCode, KERBEROS_DISABLED); StringBuilder builder = new StringBuilder(); @@ -162,11 +160,10 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au } protected Response formChallenge(AuthenticatorContext context, String negotiateHeader) { - ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession()); - code.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); - URI action = getActionUrl(context, code, KERBEROS_DISABLED); + String accessCode = context.generateAccessCode(); + URI action = getActionUrl(context, accessCode, KERBEROS_DISABLED); return context.getSession().getProvider(LoginFormsProvider.class) - .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()) + .setClientSessionCode(accessCode) .setActionUri(action) .setStatus(Response.Status.UNAUTHORIZED) .setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader) @@ -181,8 +178,7 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au } @Override - public String getRequiredAction() { - return null; + public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) { } @Override 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/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index b3716b9fcd..1a3bba4318 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -172,7 +172,7 @@ public class LoginActionsService { } else if (!clientCode.isActionActive(requiredAction)) { event.client(clientCode.getClientSession().getClient()); event.error(Errors.EXPIRED_CODE); - response = ErrorPage.error(session, Messages.INVALID_CODE); + response = ErrorPage.error(session, Messages.EXPIRED_CODE); return false; } else { return true; @@ -190,7 +190,7 @@ public class LoginActionsService { } else if (!(clientCode.isActionActive(requiredAction) || clientCode.isActionActive(alternativeRequiredAction))) { event.client(clientCode.getClientSession().getClient()); event.error(Errors.EXPIRED_CODE); - response = ErrorPage.error(session, Messages.INVALID_CODE); + response = ErrorPage.error(session, Messages.EXPIRED_CODE); return false; } else { return true; @@ -958,7 +958,7 @@ public class LoginActionsService { @PathParam("action") String action) { event.event(EventType.LOGIN); if (action == null) { - logger.error("required action was null"); + logger.error("required action query param was null"); event.error(Errors.INVALID_CODE); throw new WebApplicationException(ErrorPage.error(session, Messages.INVALID_CODE)); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationFlowResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java similarity index 60% rename from services/src/main/java/org/keycloak/services/resources/admin/AuthenticationFlowResource.java rename to services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 7d2b719f11..8bf8a62526 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationFlowResource.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,23 +25,25 @@ 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; /** - * @author Pedro Igor + * @author Bill Burke */ -public class AuthenticationFlowResource { +public class AuthenticationManagementResource { private final RealmModel realm; private final KeycloakSession session; private RealmAuth auth; private AdminEventBuilder adminEvent; - private static Logger logger = Logger.getLogger(AuthenticationFlowResource.class); + private static Logger logger = Logger.getLogger(AuthenticationManagementResource.class); - public AuthenticationFlowResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) { + public AuthenticationManagementResource(RealmModel realm, KeycloakSession session, RealmAuth auth, AdminEventBuilder adminEvent) { this.realm = realm; this.session = session; this.auth = auth; @@ -177,4 +184,117 @@ public class AuthenticationFlowResource { } } + 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(model.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 2a0a1150cd..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 @@ -56,7 +56,6 @@ import javax.ws.rs.core.UriInfo; import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; @@ -239,9 +238,9 @@ public class RealmAdminResource { return fed; } - @Path("authentication-flows") - public AuthenticationFlowResource flows() { - AuthenticationFlowResource resource = new AuthenticationFlowResource(realm, session, auth, adminEvent); + @Path("authentication") + public AuthenticationManagementResource flows() { + AuthenticationManagementResource resource = new AuthenticationManagementResource(realm, session, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(resource); //resourceContext.initResource(resource); return resource; @@ -566,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; - } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java index 087f6c324a..17ca887161 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java @@ -370,7 +370,7 @@ public class ResetPasswordTest { errorPage.assertCurrent(); - assertEquals("An error occurred, please login again through your application.", errorPage.getError()); + assertEquals("Login timeout. Please login again.", errorPage.getError()); events.expectRequiredAction(EventType.RESET_PASSWORD).error("expired_code").client("test-app").user((String) null).session((String) null).clearDetails().assertEvent(); } finally {