commit
2c327d9eee
48 changed files with 1064 additions and 223 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
}, {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
30
model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java
Executable file
30
model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_4_0.java
Executable file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
82
model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java
Executable file
82
model/api/src/main/java/org/keycloak/models/RequiredActionProviderModel.java
Executable file
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void setDefaultRequiredActions(Set<String> 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<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 = 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;
|
||||
}
|
||||
}
|
|
@ -113,12 +113,6 @@ public class RealmEntity {
|
|||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
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")
|
||||
|
@ -163,6 +157,9 @@ public class RealmEntity {
|
|||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -73,4 +73,11 @@ public interface AuthenticatorContext {
|
|||
* whatever form is challenging.
|
||||
*/
|
||||
String getForwardedErrorMessage();
|
||||
|
||||
/**
|
||||
* Generates access code and updates clientsession timestamp
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String generateAccessCode();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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<String, String> config = new HashMap<String, String>();
|
||||
|
||||
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<String, String> getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public void setConfig(Map<String, String> config) {
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Path("required-actions")
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<RequiredActionProviderRepresentation> getRequiredActions() {
|
||||
List<RequiredActionProviderRepresentation> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue