Merge pull request #1389 from patriot1burke/master

access code checks
This commit is contained in:
Bill Burke 2015-06-17 15:59:23 -04:00
commit 2c327d9eee
48 changed files with 1064 additions and 223 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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,

View file

@ -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();
});

View file

@ -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);
}
}
});

View file

@ -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 {

View file

@ -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'
}, {

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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);

View file

@ -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);
}

View file

@ -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);
}

View 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);
}
}
}
}

View file

@ -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);

View 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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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());
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);
}

View file

@ -73,4 +73,11 @@ public interface AuthenticatorContext {
* whatever form is challenging.
*/
String getForwardedErrorMessage();
/**
* Generates access code and updates clientsession timestamp
*
* @return
*/
String generateAccessCode();
}

View file

@ -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
*/

View file

@ -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();
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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();

View file

@ -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));

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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 {