Merge pull request #1423 from patriot1burke/master

display flows
This commit is contained in:
Bill Burke 2015-07-02 11:44:51 -04:00
commit 68d71a6767
23 changed files with 204 additions and 40 deletions

View file

@ -17,6 +17,12 @@
<column name="PROVIDER_ID" type="VARCHAR(36)" defaultValue="basic-flow">
<constraints nullable="false"/>
</column>
<column name="TOP_LEVEL" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="BUILT_IN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="AUTHENTICATION_EXECUTION">
<column name="AUTH_FLOW_ID" type="VARCHAR(36)">

View file

@ -1041,6 +1041,9 @@ module.config([ '$routeProvider', function($routeProvider) {
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
flows : function(AuthenticationFlowsLoader) {
return AuthenticationFlowsLoader();
}
},
controller : 'AuthenticationFlowsCtrl'

View file

@ -1566,10 +1566,14 @@ module.controller('IdentityProviderMapperCreateCtrl', function($scope, realm, id
});
module.controller('AuthenticationFlowsCtrl', function($scope, realm, AuthenticationExecutions, Notifications, Dialog, $location) {
module.controller('AuthenticationFlowsCtrl', function($scope, realm, flows, AuthenticationExecutions, Notifications, Dialog, $location) {
$scope.realm = realm;
$scope.flows = flows;
if (flows.length > 0) {
$scope.flow = flows[0];
}
var setupForm = function() {
AuthenticationExecutions.query({realm: realm.realm, alias: 'browser'}, function(data) {
AuthenticationExecutions.query({realm: realm.realm, alias: $scope.flow.alias}, function(data) {
$scope.executions = data;
$scope.flowmax = 0;
for (var i = 0; i < $scope.executions.length; i++ ) {
@ -1591,13 +1595,13 @@ module.controller('AuthenticationFlowsCtrl', function($scope, realm, Authenticat
$scope.updateExecution = function(execution) {
var copy = angular.copy(execution);
delete copy.empties;
AuthenticationExecutions.update({realm: realm.realm, alias: 'browser'}, copy, function() {
AuthenticationExecutions.update({realm: realm.realm, alias: $scope.flow.alias}, copy, function() {
Notifications.success("Auth requirement updated");
setupForm();
});
};
$scope.setupForm = setupForm;
setupForm();

View file

@ -340,3 +340,13 @@ module.factory('IdentityProviderMapperLoader', function(Loader, IdentityProvider
});
});
module.factory('AuthenticationFlowsLoader', function(Loader, AuthenticationFlows, $route, $q) {
return Loader.query(AuthenticationFlows, function() {
return {
realm : $route.current.params.realm
}
});
});

View file

@ -1079,7 +1079,7 @@ module.factory('IdentityProviderMapper', function($resource) {
});
module.factory('AuthenticationExecutions', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/authentication/flow/:alias/executions', {
return $resource(authUrl + '/admin/realms/:realm/authentication/flows/:alias/executions', {
realm : '@realm',
alias : '@alias'
}, {
@ -1089,4 +1089,10 @@ module.factory('AuthenticationExecutions', function($resource) {
});
});
module.factory('AuthenticationFlows', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/authentication/flows', {
realm : '@realm'
});
});

View file

@ -5,22 +5,16 @@
<table class="table table-striped table-bordered">
<thead>
<!--
<tr>
<th class="kc-table-actions" colspan="5">
<div class="form-inline">
<div class="form-group">
<div class="input-group">
<input type="text" placeholder="Search..." data-ng-model="search.name" class="form-control search" onkeyup="if(event.keyCode == 13){$(this).next('I').click();}">
<div class="input-group-addon">
<i class="fa fa-search" type="submit"></i>
</div>
</div>
</div>
<th colspan="5" class="kc-table-actions">
<div class="dropdown pull-left">
<select class="form-control" ng-model="flow"
ng-options="flow.alias for flow in flows"
data-ng-change="setupForm()">
</select>
</div>
</th>
</tr>
-->
<tr data-ng-hide="executions.length == 0">
<th colspan="2">Auth Type</th>
<th colspan="{{flowmax}}">Requirement</th>

View file

@ -1,5 +1,5 @@
<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] == 'flows'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/flows">Flows</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>
<li ng-class="{active: path[3] == 'password-policy'}" data-ng-show="access.viewRealm"><a href="#/realms/{{realm.realm}}/authentication/password-policy">Password Policy</a></li>
</ul>

View file

@ -13,6 +13,8 @@ public class AuthenticationFlowModel implements Serializable {
private String alias;
private String description;
private String providerId;
private boolean topLevel;
private boolean builtIn;
public String getId() {
return id;
@ -45,4 +47,20 @@ public class AuthenticationFlowModel implements Serializable {
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public boolean isTopLevel() {
return topLevel;
}
public void setTopLevel(boolean topLevel) {
this.topLevel = topLevel;
}
public boolean isBuiltIn() {
return builtIn;
}
public void setBuiltIn(boolean builtIn) {
this.builtIn = builtIn;
}
}

View file

@ -13,6 +13,8 @@ public class AuthenticationFlowEntity {
protected String alias;
protected String description;
protected String providerId;
private boolean topLevel;
private boolean builtIn;
List<AuthenticationExecutionEntity> executions = new ArrayList<AuthenticationExecutionEntity>();
public String getId() {
@ -54,4 +56,20 @@ public class AuthenticationFlowEntity {
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public boolean isTopLevel() {
return topLevel;
}
public void setTopLevel(boolean topLevel) {
this.topLevel = topLevel;
}
public boolean isBuiltIn() {
return builtIn;
}
public void setBuiltIn(boolean builtIn) {
this.builtIn = builtIn;
}
}

View file

@ -30,12 +30,16 @@ public class DefaultAuthenticationFlows {
registrationFlow.setAlias(REGISTRATION_FLOW);
registrationFlow.setDescription("registration flow");
registrationFlow.setProviderId("basic-flow");
registrationFlow.setTopLevel(true);
registrationFlow.setBuiltIn(true);
registrationFlow = realm.addAuthenticationFlow(registrationFlow);
AuthenticationFlowModel registrationFormFlow = new AuthenticationFlowModel();
registrationFormFlow.setAlias(REGISTRATION_FORM_FLOW);
registrationFormFlow.setDescription("registration form");
registrationFormFlow.setProviderId("form-flow");
registrationFormFlow.setTopLevel(false);
registrationFormFlow.setBuiltIn(true);
registrationFormFlow = realm.addAuthenticationFlow(registrationFormFlow);
AuthenticationExecutionModel execution;
@ -103,6 +107,8 @@ public class DefaultAuthenticationFlows {
browser.setAlias(BROWSER_FLOW);
browser.setDescription("browser based authentication");
browser.setProviderId("basic-flow");
browser.setTopLevel(true);
browser.setBuiltIn(true);
browser = realm.addAuthenticationFlow(browser);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId());
@ -123,6 +129,8 @@ public class DefaultAuthenticationFlows {
AuthenticationFlowModel forms = new AuthenticationFlowModel();
forms.setTopLevel(false);
forms.setBuiltIn(true);
forms.setAlias(LOGIN_FORMS_FLOW);
forms.setDescription("Username, password, otp and other auth forms.");
forms.setProviderId("basic-flow");

View file

@ -1234,6 +1234,8 @@ public class RealmAdapter implements RealmModel {
model.setAlias(entity.getAlias());
model.setDescription(entity.getDescription());
model.setProviderId(entity.getProviderId());
model.setBuiltIn(entity.isBuiltIn());
model.setTopLevel(entity.isTopLevel());
return model;
}
@ -1268,6 +1270,8 @@ public class RealmAdapter implements RealmModel {
toUpdate.setAlias(model.getAlias());
toUpdate.setDescription(model.getDescription());
toUpdate.setProviderId(model.getProviderId());
toUpdate.setBuiltIn(model.isBuiltIn());
toUpdate.setTopLevel(model.isTopLevel());
}
@ -1278,6 +1282,8 @@ public class RealmAdapter implements RealmModel {
entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
entity.setBuiltIn(model.isBuiltIn());
entity.setTopLevel(model.isTopLevel());
realm.getAuthenticationFlows().add(entity);
model.setId(entity.getId());
return model;

View file

@ -1544,6 +1544,8 @@ public class RealmAdapter implements RealmModel {
model.setAlias(entity.getAlias());
model.setProviderId(entity.getProviderId());
model.setDescription(entity.getDescription());
model.setBuiltIn(entity.isBuiltIn());
model.setTopLevel(entity.isTopLevel());
return model;
}
@ -1569,6 +1571,8 @@ public class RealmAdapter implements RealmModel {
entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
entity.setBuiltIn(model.isBuiltIn());
entity.setTopLevel(model.isTopLevel());
}
@ -1579,6 +1583,8 @@ public class RealmAdapter implements RealmModel {
entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
entity.setBuiltIn(model.isBuiltIn());
entity.setTopLevel(model.isTopLevel());
entity.setRealm(realm);
realm.getAuthenticationFlows().add(entity);
em.persist(entity);

View file

@ -42,6 +42,13 @@ public class AuthenticationFlowEntity {
@Column(name="DESCRIPTION")
protected String description;
@Column(name="TOP_LEVEL")
protected boolean topLevel;
@Column(name="BUILT_IN")
protected boolean builtIn;
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "parentFlow")
Collection<AuthenticationExecutionEntity> executions = new ArrayList<AuthenticationExecutionEntity>();
public String getId() {
@ -91,4 +98,20 @@ public class AuthenticationFlowEntity {
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public boolean isTopLevel() {
return topLevel;
}
public void setTopLevel(boolean topLevel) {
this.topLevel = topLevel;
}
public boolean isBuiltIn() {
return builtIn;
}
public void setBuiltIn(boolean builtIn) {
this.builtIn = builtIn;
}
}

View file

@ -1308,6 +1308,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
model.setId(entity.getId());
model.setAlias(entity.getAlias());
model.setDescription(entity.getDescription());
model.setBuiltIn(entity.isBuiltIn());
model.setTopLevel(entity.isTopLevel());
return model;
}
@ -1342,6 +1344,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
toUpdate.setAlias(model.getAlias());
toUpdate.setDescription(model.getDescription());
toUpdate.setProviderId(model.getProviderId());
toUpdate.setBuiltIn(model.isBuiltIn());
toUpdate.setTopLevel(model.isTopLevel());
updateMongoEntity();
}
@ -1352,6 +1356,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
entity.setBuiltIn(model.isBuiltIn());
entity.setTopLevel(model.isTopLevel());
getMongoEntity().getAuthenticationFlows().add(entity);
model.setId(entity.getId());
updateMongoEntity();

View file

@ -7,6 +7,9 @@ import javax.ws.rs.core.Response;
* @version $Revision: 1 $
*/
public interface AuthenticationFlow {
String BASIC_FLOW = "basic-flow";
String FORM_FLOW = "form-flow";
Response processAction(String actionExecution);
Response processFlow();
}

View file

@ -7,7 +7,6 @@ import org.keycloak.authentication.authenticators.AbstractFormAuthenticator;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
@ -466,11 +465,11 @@ public class AuthenticationProcessor {
logger.error("Unknown flow to execute with");
throw new AuthException(Error.INTERNAL_ERROR);
}
if (flow.getProviderId() == null || flow.getProviderId().equals("basic-flow")) {
if (flow.getProviderId() == null || flow.getProviderId().equals(AuthenticationFlow.BASIC_FLOW)) {
DefaultAuthenticationFlow flowExecution = new DefaultAuthenticationFlow(this, flow);
return flowExecution;
} else if (flow.getProviderId().equals("form-flow")) {
} else if (flow.getProviderId().equals(AuthenticationFlow.FORM_FLOW)) {
FormAuthenticationFlow flowExecution = new FormAuthenticationFlow(this, execution);
return flowExecution;
}

View file

@ -73,7 +73,7 @@ public class SpnegoAuthenticatorFactory implements AuthenticatorFactory {
@Override
public String getDisplayType() {
return "SPNEGO";
return "Kerberos";
}
@Override

View file

@ -51,9 +51,13 @@ public class RegistrationPage implements FormAuthenticator, FormAuthenticatorFac
return false;
}
private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED
};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return new AuthenticationExecutionModel.Requirement[0];
return REQUIREMENT_CHOICES;
}
@Override

View file

@ -114,9 +114,13 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
return false;
}
private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED
};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return new AuthenticationExecutionModel.Requirement[0];
return REQUIREMENT_CHOICES;
}
@Override

View file

@ -120,11 +120,14 @@ public class RegistrationProfile implements FormAction, FormActionFactory {
return false;
}
private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED
};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return new AuthenticationExecutionModel.Requirement[0];
return REQUIREMENT_CHOICES;
}
@Override
public FormAction create(KeycloakSession session) {
return this;

View file

@ -23,6 +23,8 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.JsonSerialization;
@ -38,7 +40,7 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RegistrationRecaptcha implements FormAction, FormActionFactory {
public class RegistrationRecaptcha implements FormAction, FormActionFactory, ConfiguredProvider {
public static final String G_RECAPTCHA_RESPONSE = "g-recaptcha-response";
public static final String RECAPTCHA_REFERENCE_CATEGORY = "recaptcha";
protected static Logger logger = Logger.getLogger(RegistrationRecaptcha.class);
@ -60,11 +62,14 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory {
return true;
}
private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED
};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return new AuthenticationExecutionModel.Requirement[0];
return REQUIREMENT_CHOICES;
}
@Override
public void buildPage(FormContext context, LoginFormsProvider form) {
AuthenticatorConfigModel captchaConfig = context.getAuthenticatorConfig();
@ -174,4 +179,14 @@ public class RegistrationRecaptcha implements FormAction, FormActionFactory {
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return null;
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return null;
}
}

View file

@ -158,11 +158,14 @@ public class RegistrationUserCreation implements FormAction, FormActionFactory {
return false;
}
private static AuthenticationExecutionModel.Requirement[] REQUIREMENT_CHOICES = {
AuthenticationExecutionModel.Requirement.REQUIRED,
AuthenticationExecutionModel.Requirement.DISABLED
};
@Override
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {
return new AuthenticationExecutionModel.Requirement[0];
return REQUIREMENT_CHOICES;
}
@Override
public FormAction create(KeycloakSession session) {
return this;

View file

@ -3,9 +3,14 @@ package org.keycloak.services.resources.admin;
import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.authentication.AuthenticationFlow;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.authentication.AuthenticatorUtil;
import org.keycloak.authentication.ConfigurableAuthenticatorFactory;
import org.keycloak.authentication.DefaultAuthenticationFlow;
import org.keycloak.authentication.FormAction;
import org.keycloak.authentication.FormActionFactory;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.KeycloakSession;
@ -104,7 +109,21 @@ public class AuthenticationManagementResource {
}
}
@Path("/flow/{flowAlias}/executions")
@Path("/flows")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<AuthenticationFlowModel> getFlows() {
List<AuthenticationFlowModel> flows = new LinkedList<>();
for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) {
if (flow.isTopLevel()) {
flows.add(flow);
}
}
return flows;
}
@Path("/flows/{flowAlias}/executions")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
@ -124,11 +143,15 @@ public class AuthenticationManagementResource {
rep.setRequirementChoices(new LinkedList<String>());
if (execution.isAutheticatorFlow()) {
AuthenticationFlowModel flowRef = realm.getAuthenticationFlowById(execution.getFlowId());
if (AuthenticationFlow.BASIC_FLOW.equals(flowRef.getProviderId())) {
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name());
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.REQUIRED.name());
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.DISABLED.name());
} else if (AuthenticationFlow.FORM_FLOW.equals(flowRef.getProviderId())) {
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.REQUIRED.name());
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.DISABLED.name());
}
rep.setReferenceType(flowRef.getAlias());
rep.setExecution(execution.getId());
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name());
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.REQUIRED.name());
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.DISABLED.name());
rep.setConfigurable(false);
rep.setExecution(execution.getId());
rep.setRequirement(execution.getRequirement().name());
@ -137,9 +160,11 @@ public class AuthenticationManagementResource {
if (!flow.getId().equals(execution.getParentFlow())) {
rep.setSubFlow(true);
}
AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, execution.getAuthenticator());
if (factory.getReferenceCategory() == null) continue;
rep.setReferenceType(factory.getReferenceCategory());
ConfigurableAuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, execution.getAuthenticator());
if (factory == null) {
factory = (FormActionFactory)session.getKeycloakSessionFactory().getProviderFactory(FormAction.class, execution.getAuthenticator());
}
rep.setReferenceType(factory.getDisplayType());
rep.setConfigurable(factory.isConfigurable());
for (AuthenticationExecutionModel.Requirement choice : factory.getRequirementChoices()) {
rep.getRequirementChoices().add(choice.name());
@ -154,7 +179,7 @@ public class AuthenticationManagementResource {
return Response.ok(result).build();
}
@Path("/flow/{flowAlias}/executions")
@Path("/flows/{flowAlias}/executions")
@PUT
@NoCache
@Consumes(MediaType.APPLICATION_JSON)