keycloak-aplcache
Changes
forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js 28(+28 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/authentication-flows.html 6(+4 -2)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html 27(+27 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-detail.html 2(+1 -1)
forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html 4(+4 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java 17(+13 -4)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java 33(+24 -9)
model/jpa/src/main/java/org/keycloak/models/jpa/entities/RequiredActionProviderEntity.java 118(+118 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java 7(+5 -2)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java 121(+93 -28)
services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java 17(+5 -12)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java 7(+5 -2)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java 7(+5 -2)
services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java 3(+1 -2)
services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java 14(+8 -6)
services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java 16(+6 -10)
Details
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index bb38914..08d51a9 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 @@
<class>org.keycloak.models.jpa.entities.AuthenticationFlowEntity</class>
<class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class>
<class>org.keycloak.models.jpa.entities.AuthenticatorEntity</class>
+ <class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
<!-- JpaUserSessionProvider -->
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
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 80f0e36..fa92731 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 @@
<delete tableName="CLIENT_SESSION"/>
<delete tableName="USER_SESSION_NOTE"/>
<delete tableName="USER_SESSION"/>
- <createTable tableName="DEFAULT_REQUIRED_ACTIONS">
- <column name="REALM_ID" type="VARCHAR(36)">
- <constraints nullable="false"/>
- </column>
- <column name="VALUE" type="VARCHAR(36)"/>
- </createTable>
<addColumn tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" type="VARCHAR(36)">
<constraints nullable="false"/>
@@ -78,9 +72,36 @@
<constraints nullable="false"/>
</column>
</createTable>
+ <createTable tableName="REQUIRED_ACTION_PROVIDER">
+ <column name="ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="ALIAS" type="VARCHAR(255)"/>
+ <column name="NAME" type="VARCHAR(255)"/>
+ <column name="REALM_ID" type="VARCHAR(36)"/>
+ <column name="ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ <column name="DEFAULT_ACTION" type="BOOLEAN" defaultValueBoolean="false">
+ <constraints nullable="false"/>
+ </column>
+ <column name="PROVIDER_ID" type="VARCHAR(255)"/>
+ </createTable>
+ <createTable tableName="REQUIRED_ACTION_CONFIG">
+ <column name="REQUIRED_ACTION_ID" type="VARCHAR(36)">
+ <constraints nullable="false"/>
+ </column>
+ <column name="VALUE" type="CLOB"/>
+ <column name="NAME" type="VARCHAR(255)">
+ <constraints nullable="false"/>
+ </column>
+ </createTable>
+
+ <addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_REQ_ACT_PRV_PK" tableName="REQUIRED_ACTION_PROVIDER"/>
+ <addPrimaryKey columnNames="REQUIRED_ACTION_ID, NAME" constraintName="CONSTRAINT_REQ_ACT_CFG_PK" tableName="REQUIRED_ACTION_CONFIG"/>
<addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTR_CL_USR_SES_NOTE" tableName="CLIENT_USER_SESSION_NOTE"/>
+ <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REQUIRED_ACTION_PROVIDER" constraintName="FK_REQ_ACT_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_USER_SESSION_NOTE" constraintName="FK_CL_USR_SES_NOTE" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
<dropColumn tableName="CLIENT_SESSION" columnName="ACTION"/>
- <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="DEFAULT_REQUIRED_ACTIONS" constraintName="FK_DEF_REQ_ACTS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
</changeSet>
</databaseChangeLog>
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 822e688..f31a009 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 a88ffd7..a9c7580 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 a2573cb..2ff1273 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 3a492bb..5ebc0a5 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 067bb20..8b641c1 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 b3ecf71..cfc4f89 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 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
- <h1><strong>Authentication Flows</strong> {{realm.realm|capitalize}}</h1>
+ <h1>Authentication</h1>
+
+ <kc-tabs-authentication></kc-tabs-authentication>
<table class="table table-striped table-bordered">
<thead>
@@ -25,7 +27,7 @@
</tr>
</thead>
<tbody>
- <tr ng-repeat="execution in executions">
+ <tr ng-repeat="execution in executions" data-ng-show="executions.length > 0">
<td ng-show="execution.subFlow"></td>
<td><h2>{{execution.referenceType}}</h2></td>
<td ng-hide="execution.subFlow"></td>
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 0000000..bdde0c5
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html
@@ -0,0 +1,27 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+ <h1>Authentication</h1>
+
+ <kc-tabs-authentication></kc-tabs-authentication>
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr data-ng-hide="requiredActions.length == 0">
+ <th>Required Action</th>
+ <th>Enabled</th>
+ <th>Default Action <i class="fa fa-question-circle text-muted" tooltip="If enabled, any new user will have this required action assigned to it."></i></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr ng-repeat="requiredAction in requiredActions" data-ng-show="requiredActions.length > 0">
+ <td>{{requiredAction.name}}</td>
+ <td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)"></td>
+ <td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)"></td>
+ </tr>
+ <tr data-ng-show="requiredActions.length == 0">
+ <td>No required actions configured</td>
+ </tr>
+ </tbody>
+ </table>
+
+</div>
+
+<kc-menu></kc-menu>
\ 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 09a3fc4..9c851ee 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 @@
<div class="col-md-6">
<select ui-select2 id="reqActions" ng-model="user.requiredActions" data-placeholder="Select an action..." multiple>
- <option ng-repeat="action in userReqActionList" value="{{action.id}}">{{action.text}}</option>
+ <option ng-repeat="action in userReqActionList" value="{{action.alias}}">{{action.name}}</option>
</select>
</div>
<kc-tooltip>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.</kc-tooltip>
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 ba09d84..a931647 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 @@
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles' || (path[1] == 'role' && path[3] != 'clients')) && 'active'"><a href="#/realms/{{realm.realm}}/roles">Roles</a></li>
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'identity-provider-settings' || path[2] == 'identity-provider-mappers') && 'active'"><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
<li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'user-federation' || path[2] == 'user-federation') && 'active'"><a href="#/realms/{{realm.realm}}/user-federation">User Federation</a></li>
- <li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'authentication' || path[2] == 'authentication') && 'active'"><a href="#/realms/{{realm.realm}}/authentication">Authentication</a></li>
+ <li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'authentication' || path[2] == 'authentication') && 'active'"><a href="#/realms/{{realm.realm}}/authentication/flows">Authentication</a></li>
</ul>
</div>
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 0000000..6fba9f1
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-authentication.html
@@ -0,0 +1,4 @@
+<ul class="nav nav-tabs">
+ <li ng-class="{active: path[3] == 'flows'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/flows">Authenticators</a></li>
+ <li ng-class="{active: path[3] == 'required-actions'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/required-actions">Required Actions</a></li>
+</ul>
\ 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 df24b3d..cb08418 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 722bc5e..b791003 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 1b68528..43209a8 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<RealmModel> 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 0000000..0b7e8f8
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class MigrateTo1_4_0 {
+ public static final ModelVersion VERSION = new ModelVersion("1.4.0");
+
+
+ public void migrate(KeycloakSession session) {
+ List<RealmModel> 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/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index b7f2588..0548d49 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<IdentityProviderMapperEntity> identityProviderMappers = new ArrayList<IdentityProviderMapperEntity>();
private List<AuthenticationFlowEntity> authenticationFlows = new ArrayList<>();
private List<AuthenticatorEntity> authenticators = new ArrayList<>();
- private List<String> defaultRequiredActions = new ArrayList<>();
+ private List<RequiredActionProviderEntity> requiredActionProviders = new ArrayList<>();
public String getName() {
@@ -504,12 +504,12 @@ public class RealmEntity extends AbstractIdentifiableEntity {
this.authenticators = authenticators;
}
- public List<String> getDefaultRequiredActions() {
- return defaultRequiredActions;
+ public List<RequiredActionProviderEntity> getRequiredActionProviders() {
+ return requiredActionProviders;
}
- public void setDefaultRequiredActions(List<String> defaultRequiredActions) {
- this.defaultRequiredActions = defaultRequiredActions;
+ public void setRequiredActionProviders(List<RequiredActionProviderEntity> 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 0000000..30fcf3e
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @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<String, String> 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<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+}
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 6366325..8aaec24 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<String> getDefaultRequiredActions();
-
- void addDefaultRequiredAction(String action);
- void removeDefaultRequiredAction(String action);
-
- void setDefaultRequiredActions(Set<String> action);
-
// Key is clientId
Map<String, ClientModel> getClientNameMap();
@@ -206,6 +199,13 @@ public interface RealmModel extends RoleContainerModel {
void removeAuthenticator(AuthenticatorModel model);
AuthenticatorModel getAuthenticatorById(String id);
+ List<RequiredActionProviderModel> getRequiredActionProviders();
+ RequiredActionProviderModel addRequiredActionProvider(RequiredActionProviderModel model);
+ void updateRequiredActionProvider(RequiredActionProviderModel model);
+ void removeRequiredActionProvider(RequiredActionProviderModel model);
+ RequiredActionProviderModel getRequiredActionProviderById(String id);
+ RequiredActionProviderModel getRequiredActionProviderByAlias(String alias);
+
List<IdentityProviderModel> 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 0000000..a32a23c
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+* @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<String, String> config = new HashMap<String, String>();
+
+
+ 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<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> 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 0000000..ab5468c
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @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 61cc52b..c5b49d0 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<String, String> 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<RequiredActionProviderModel> getRequiredActionProviders() {
+ List<RequiredActionProviderModel> 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<UserFederationMapperModel> getUserFederationMappers() {
@@ -1563,36 +1655,4 @@ public class RealmAdapter implements RealmModel {
return mapper;
}
- @Override
- public Set<String> getDefaultRequiredActions() {
- Set<String> result = new HashSet<String>();
- if (realm.getDefaultRequiredActions() != null) {
- result.addAll(realm.getDefaultRequiredActions());
- }
- return result;
- }
-
- @Override
- public void addDefaultRequiredAction(String action) {
- Set<String> actions = getDefaultRequiredActions();
- actions.add(action);
- setDefaultRequiredActions(actions);
-
- }
-
- @Override
- public void removeDefaultRequiredAction(String action) {
- Set<String> actions = getDefaultRequiredActions();
- actions.remove(action);
- setDefaultRequiredActions(actions);
-
- }
-
- @Override
- public void setDefaultRequiredActions(Set<String> action) {
- List<String> result = new ArrayList<String>();
- result.addAll(action);
- realm.setDefaultRequiredActions(result);
-
- }
-}
+ }
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 cf2f21d..eb28d30 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/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 3ee7852..b08748d 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<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
private Map<String, AuthenticatorModel> authenticators = new HashMap<>();
+ private Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
+ private Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
private MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
private Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
@@ -100,7 +103,6 @@ public class CachedRealm implements Serializable {
private Set<String> supportedLocales = new HashSet<String>();
private String defaultLocale;
private MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
- private Set<String> 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<String> getDefaultRequiredActions() {
- return defaultRequiredActions;
+ public Map<String, RequiredActionProviderModel> getRequiredActionProviders() {
+ return requiredActionProviders;
+ }
+
+ public Map<String, RequiredActionProviderModel> getRequiredActionProvidersByAlias() {
+ return requiredActionProvidersByAlias;
}
}
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 2db7ca2..cf1bcb1 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<String> getDefaultRequiredActions() {
- return cached.getDefaultRequiredActions();
+ public List<RequiredActionProviderModel> getRequiredActionProviders() {
+ if (updated != null) return updated.getRequiredActionProviders();
+ List<RequiredActionProviderModel> 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 removeDefaultRequiredAction(String action) {
+ public void updateRequiredActionProvider(RequiredActionProviderModel model) {
getDelegateForUpdate();
- updated.removeDefaultRequiredAction(action);
+ updated.updateRequiredActionProvider(model);
}
@Override
- public void setDefaultRequiredActions(Set<String> action) {
+ public void removeRequiredActionProvider(RequiredActionProviderModel model) {
getDelegateForUpdate();
- updated.setDefaultRequiredActions(action);
+ updated.removeRequiredActionProvider(model);
+
+ }
+ @Override
+ 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/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 5ccb017..be77599 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
@@ -114,12 +114,6 @@ public class RealmEntity {
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@ElementCollection
- @Column(name="VALUE")
- @CollectionTable(name = "DEFAULT_REQUIRED_ACTIONS", joinColumns={ @JoinColumn(name="REALM_ID") })
- protected Set<String> defaultRequiredActions = new HashSet<String>();
-
-
- @ElementCollection
@MapKeyColumn(name="NAME")
@Column(name="VALUE")
@CollectionTable(name="REALM_SMTP_CONFIG", joinColumns={ @JoinColumn(name="REALM_ID") })
@@ -164,6 +158,9 @@ public class RealmEntity {
Collection<AuthenticatorEntity> authenticators = new ArrayList<>();
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+ Collection<RequiredActionProviderEntity> requiredActionProviders = new ArrayList<>();
+
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<AuthenticationFlowEntity> authenticationFlows = new ArrayList<>();
@@ -567,6 +564,14 @@ public class RealmEntity {
this.authenticators = authenticators;
}
+ public Collection<RequiredActionProviderEntity> getRequiredActionProviders() {
+ return requiredActionProviders;
+ }
+
+ public void setRequiredActionProviders(Collection<RequiredActionProviderEntity> requiredActionProviders) {
+ this.requiredActionProviders = requiredActionProviders;
+ }
+
public Collection<AuthenticationFlowEntity> getAuthenticationFlows() {
return authenticationFlows;
}
@@ -575,12 +580,5 @@ public class RealmEntity {
this.authenticationFlows = authenticationFlows;
}
- public Set<String> getDefaultRequiredActions() {
- return defaultRequiredActions;
- }
-
- public void setDefaultRequiredActions(Set<String> 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 0000000..4c5ecdd
--- /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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @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<String, String> 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<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
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 1c45bde..ca30006 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 33ea232..5c62dcd 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<String> getDefaultRequiredActions() {
- Set<String> result = new HashSet<String>();
- result.addAll(realm.getDefaultRequiredActions());
- return result;
+ 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 removeRequiredActionProvider(RequiredActionProviderModel model) {
+ RequiredActionProviderEntity entity = em.find(RequiredActionProviderEntity.class, model.getId());
+ if (entity == null) return;
+ em.remove(entity);
+ em.flush();
+ }
@Override
- public void setDefaultRequiredActions(Set<String> actions) {
- realm.setDefaultRequiredActions(actions);
+ public RequiredActionProviderModel getRequiredActionProviderById(String id) {
+ RequiredActionProviderEntity entity = em.find(RequiredActionProviderEntity.class, id);
+ if (entity == null) return null;
+ return entityToModel(entity);
}
- @Override
- public void addDefaultRequiredAction(String action) {
- realm.getDefaultRequiredActions().add(action);
+ 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<String, String> config = new HashMap<>();
+ if (entity.getConfig() != null) config.putAll(entity.getConfig());
+ model.setConfig(config);
+ return model;
}
@Override
- public void removeDefaultRequiredAction(String action) {
- realm.getDefaultRequiredActions().remove(action);
- }
+ 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<RequiredActionProviderModel> getRequiredActionProviders() {
+ List<RequiredActionProviderModel> 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/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 abf8121..6c692de 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 45456de..bf1627f 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<MongoRealmEntity> 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<String, String> 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<RequiredActionProviderModel> getRequiredActionProviders() {
+ List<RequiredActionProviderModel> 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<UserFederationMapperModel> getUserFederationMappers() {
@@ -1648,32 +1741,4 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
mapper.setConfig(config);
return mapper;
}
-
- @Override
- public Set<String> getDefaultRequiredActions() {
- Set<String> result = new HashSet<String>();
- result.addAll(realm.getDefaultRequiredActions());
- return result;
- }
-
-
-
- @Override
- public void setDefaultRequiredActions(Set<String> actions) {
- List<String> result = new ArrayList<String>();
- 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 af6a671..fcc3a76 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 ee9d435..8245f82 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 a8f5aad..637d3a8 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 561e60d..a6ac452 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<Authenticator>, 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<Authenticator>, 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 44d11fd..9184034 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 1455b2f..a4d6430 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 bac37f7..56ef93a 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 fb393e2..cdd49b9 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 62e9643..6c3759a 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 325f3cd..bce060b 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 3970fa8..eb6c1ee 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 56aface..9695a4e 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<String> 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 41f5ac6..99b893c 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<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
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 2a0a115..5280a97 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<Map<String, String>> getRequiredActions() {
- List<Map<String, String>> list = new LinkedList<>();
- for (ProviderFactory factory : session.getKeycloakSessionFactory().getProviderFactories(RequiredActionProvider.class)) {
- RequiredActionFactory actionFactory = (RequiredActionFactory)factory;
- Map<String, String> data = new HashMap<>();
- data.put("id", actionFactory.getId());
- data.put("text", actionFactory.getDisplayText());
- list.add(data);
- }
- return list;
- }
}
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 b3716b9..1a3bba4 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/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 087f6c3..17ca887 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 {