form auth

This commit is contained in:
Bill Burke 2015-06-24 21:07:38 -04:00
parent 000159226d
commit 064d677fdc
32 changed files with 794 additions and 235 deletions

View file

@ -12,6 +12,16 @@
<constraints nullable="true"/> <constraints nullable="true"/>
</column> </column>
</addColumn> </addColumn>
<addColumn tableName="AUTHENTICATION_FLOW">
<column name="PROVIDER_ID" type="VARCHAR(36)" defaultValue="basic-flow">
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="AUTHENTICATION_EXECUTION">
<column name="AUTH_FLOW_ID" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
</addColumn>
<dropColumn tableName="AUTHENTICATOR" columnName="PROVIDER_ID"/> <dropColumn tableName="AUTHENTICATOR" columnName="PROVIDER_ID"/>
<renameTable oldTableName="AUTHENTICATOR_CONFIG" newTableName="AUTHENTICATOR_CONFIG_ENTRY"/> <renameTable oldTableName="AUTHENTICATOR_CONFIG" newTableName="AUTHENTICATOR_CONFIG_ENTRY"/>
<renameTable oldTableName="AUTHENTICATOR" newTableName="AUTHENTICATOR_CONFIG"/> <renameTable oldTableName="AUTHENTICATOR" newTableName="AUTHENTICATOR_CONFIG"/>

View file

@ -22,6 +22,7 @@ public class AuthenticationExecutionModel implements Serializable {
private String id; private String id;
private String authenticatorConfig; private String authenticatorConfig;
private String authenticator; private String authenticator;
private String flowId;
private boolean autheticatorFlow; private boolean autheticatorFlow;
private Requirement requirement; private Requirement requirement;
private boolean userSetupAllowed; private boolean userSetupAllowed;
@ -84,6 +85,19 @@ public class AuthenticationExecutionModel implements Serializable {
this.parentFlow = parentFlow; this.parentFlow = parentFlow;
} }
/**
* If this execution is a flow, this is the flowId pointing to an AuthenticationFlowModel
*
* @return
*/
public String getFlowId() {
return flowId;
}
public void setFlowId(String flowId) {
this.flowId = flowId;
}
/** /**
* Is the referenced authenticator a flow? * Is the referenced authenticator a flow?
* *

View file

@ -12,6 +12,7 @@ public class AuthenticationFlowModel implements Serializable {
private String id; private String id;
private String alias; private String alias;
private String description; private String description;
private String providerId;
public String getId() { public String getId() {
return id; return id;
@ -36,4 +37,12 @@ public class AuthenticationFlowModel implements Serializable {
public void setDescription(String description) { public void setDescription(String description) {
this.description = description; this.description = description;
} }
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
} }

View file

@ -10,6 +10,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
public class AuthenticationExecutionEntity { public class AuthenticationExecutionEntity {
protected String id; protected String id;
protected String authenticator; protected String authenticator;
protected String flowId;
protected AuthenticationExecutionModel.Requirement requirement; protected AuthenticationExecutionModel.Requirement requirement;
protected int priority; protected int priority;
private boolean userSetupAllowed; private boolean userSetupAllowed;
@ -71,4 +72,12 @@ public class AuthenticationExecutionEntity {
public void setParentFlow(String parentFlow) { public void setParentFlow(String parentFlow) {
this.parentFlow = parentFlow; this.parentFlow = parentFlow;
} }
public String getFlowId() {
return flowId;
}
public void setFlowId(String flowId) {
this.flowId = flowId;
}
} }

View file

@ -12,6 +12,8 @@ public class AuthenticationFlowEntity {
protected String id; protected String id;
protected String alias; protected String alias;
protected String description; protected String description;
protected String providerId;
List<AuthenticationExecutionEntity> executions = new ArrayList<AuthenticationExecutionEntity>(); List<AuthenticationExecutionEntity> executions = new ArrayList<AuthenticationExecutionEntity>();
public String getId() { public String getId() {
return id; return id;
@ -44,4 +46,12 @@ public class AuthenticationFlowEntity {
public void setExecutions(List<AuthenticationExecutionEntity> executions) { public void setExecutions(List<AuthenticationExecutionEntity> executions) {
this.executions = executions; this.executions = executions;
} }
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
} }

View file

@ -18,6 +18,7 @@ public class DefaultAuthenticationFlows {
AuthenticationFlowModel browser = new AuthenticationFlowModel(); AuthenticationFlowModel browser = new AuthenticationFlowModel();
browser.setAlias(BROWSER_FLOW); browser.setAlias(BROWSER_FLOW);
browser.setDescription("browser based authentication"); browser.setDescription("browser based authentication");
browser.setProviderId("basic-flow");
browser = realm.addAuthenticationFlow(browser); browser = realm.addAuthenticationFlow(browser);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel(); AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId()); execution.setParentFlow(browser.getId());
@ -40,11 +41,12 @@ public class DefaultAuthenticationFlows {
AuthenticationFlowModel forms = new AuthenticationFlowModel(); AuthenticationFlowModel forms = new AuthenticationFlowModel();
forms.setAlias(FORMS_FLOW); forms.setAlias(FORMS_FLOW);
forms.setDescription("Username, password, otp and other auth forms."); forms.setDescription("Username, password, otp and other auth forms.");
forms.setProviderId("basic-flow");
forms = realm.addAuthenticationFlow(forms); forms = realm.addAuthenticationFlow(forms);
execution = new AuthenticationExecutionModel(); execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId()); execution.setParentFlow(browser.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE); execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator(forms.getId()); execution.setFlowId(forms.getId());
execution.setPriority(30); execution.setPriority(30);
execution.setUserSetupAllowed(false); execution.setUserSetupAllowed(false);
execution.setAutheticatorFlow(true); execution.setAutheticatorFlow(true);

View file

@ -1233,6 +1233,7 @@ public class RealmAdapter implements RealmModel {
model.setId(entity.getId()); model.setId(entity.getId());
model.setAlias(entity.getAlias()); model.setAlias(entity.getAlias());
model.setDescription(entity.getDescription()); model.setDescription(entity.getDescription());
model.setProviderId(entity.getProviderId());
return model; return model;
} }
@ -1266,6 +1267,7 @@ public class RealmAdapter implements RealmModel {
if (toUpdate == null) return; if (toUpdate == null) return;
toUpdate.setAlias(model.getAlias()); toUpdate.setAlias(model.getAlias());
toUpdate.setDescription(model.getDescription()); toUpdate.setDescription(model.getDescription());
toUpdate.setProviderId(model.getProviderId());
} }
@ -1275,6 +1277,7 @@ public class RealmAdapter implements RealmModel {
entity.setId(KeycloakModelUtils.generateId()); entity.setId(KeycloakModelUtils.generateId());
entity.setAlias(model.getAlias()); entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription()); entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
realm.getAuthenticationFlows().add(entity); realm.getAuthenticationFlows().add(entity);
model.setId(entity.getId()); model.setId(entity.getId());
return model; return model;
@ -1303,6 +1306,7 @@ public class RealmAdapter implements RealmModel {
model.setPriority(entity.getPriority()); model.setPriority(entity.getPriority());
model.setAuthenticator(entity.getAuthenticator()); model.setAuthenticator(entity.getAuthenticator());
model.setParentFlow(entity.getParentFlow()); model.setParentFlow(entity.getParentFlow());
model.setFlowId(entity.getFlowId());
model.setAutheticatorFlow(entity.isAuthenticatorFlow()); model.setAutheticatorFlow(entity.isAuthenticatorFlow());
return model; return model;
} }
@ -1334,6 +1338,7 @@ public class RealmAdapter implements RealmModel {
entity.setRequirement(model.getRequirement()); entity.setRequirement(model.getRequirement());
entity.setUserSetupAllowed(model.isUserSetupAllowed()); entity.setUserSetupAllowed(model.isUserSetupAllowed());
entity.setAuthenticatorFlow(model.isAutheticatorFlow()); entity.setAuthenticatorFlow(model.isAutheticatorFlow());
entity.setFlowId(model.getFlowId());
AuthenticationFlowEntity flow = getFlowEntity(model.getId()); AuthenticationFlowEntity flow = getFlowEntity(model.getId());
flow.getExecutions().add(entity); flow.getExecutions().add(entity);
model.setId(entity.getId()); model.setId(entity.getId());
@ -1355,6 +1360,7 @@ public class RealmAdapter implements RealmModel {
entity.setAuthenticator(model.getAuthenticator()); entity.setAuthenticator(model.getAuthenticator());
entity.setPriority(model.getPriority()); entity.setPriority(model.getPriority());
entity.setRequirement(model.getRequirement()); entity.setRequirement(model.getRequirement());
entity.setFlowId(model.getFlowId());
entity.setUserSetupAllowed(model.isUserSetupAllowed()); entity.setUserSetupAllowed(model.isUserSetupAllowed());
} }

View file

@ -1542,6 +1542,7 @@ public class RealmAdapter implements RealmModel {
AuthenticationFlowModel model = new AuthenticationFlowModel(); AuthenticationFlowModel model = new AuthenticationFlowModel();
model.setId(entity.getId()); model.setId(entity.getId());
model.setAlias(entity.getAlias()); model.setAlias(entity.getAlias());
model.setProviderId(entity.getProviderId());
model.setDescription(entity.getDescription()); model.setDescription(entity.getDescription());
return model; return model;
} }
@ -1567,6 +1568,7 @@ public class RealmAdapter implements RealmModel {
if (entity == null) return; if (entity == null) return;
entity.setAlias(model.getAlias()); entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription()); entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
} }
@ -1576,6 +1578,7 @@ public class RealmAdapter implements RealmModel {
entity.setId(KeycloakModelUtils.generateId()); entity.setId(KeycloakModelUtils.generateId());
entity.setAlias(model.getAlias()); entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription()); entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
entity.setRealm(realm); entity.setRealm(realm);
realm.getAuthenticationFlows().add(entity); realm.getAuthenticationFlows().add(entity);
em.persist(entity); em.persist(entity);
@ -1589,7 +1592,7 @@ public class RealmAdapter implements RealmModel {
TypedQuery<AuthenticationExecutionEntity> query = em.createNamedQuery("getAuthenticationExecutionsByFlow", AuthenticationExecutionEntity.class); TypedQuery<AuthenticationExecutionEntity> query = em.createNamedQuery("getAuthenticationExecutionsByFlow", AuthenticationExecutionEntity.class);
AuthenticationFlowEntity flow = em.getReference(AuthenticationFlowEntity.class, flowId); AuthenticationFlowEntity flow = em.getReference(AuthenticationFlowEntity.class, flowId);
query.setParameter("realm", realm); query.setParameter("realm", realm);
query.setParameter("flow", flow); query.setParameter("parentFlow", flow);
List<AuthenticationExecutionEntity> queryResult = query.getResultList(); List<AuthenticationExecutionEntity> queryResult = query.getResultList();
List<AuthenticationExecutionModel> executions = new LinkedList<>(); List<AuthenticationExecutionModel> executions = new LinkedList<>();
for (AuthenticationExecutionEntity entity : queryResult) { for (AuthenticationExecutionEntity entity : queryResult) {
@ -1607,7 +1610,8 @@ public class RealmAdapter implements RealmModel {
model.setRequirement(entity.getRequirement()); model.setRequirement(entity.getRequirement());
model.setPriority(entity.getPriority()); model.setPriority(entity.getPriority());
model.setAuthenticator(entity.getAuthenticator()); model.setAuthenticator(entity.getAuthenticator());
model.setParentFlow(entity.getFlow().getId()); model.setFlowId(entity.getFlowId());
model.setParentFlow(entity.getParentFlow().getId());
model.setAutheticatorFlow(entity.isAutheticatorFlow()); model.setAutheticatorFlow(entity.isAutheticatorFlow());
return model; return model;
} }
@ -1625,9 +1629,10 @@ public class RealmAdapter implements RealmModel {
entity.setId(KeycloakModelUtils.generateId()); entity.setId(KeycloakModelUtils.generateId());
entity.setAuthenticator(model.getAuthenticator()); entity.setAuthenticator(model.getAuthenticator());
entity.setPriority(model.getPriority()); entity.setPriority(model.getPriority());
entity.setFlowId(model.getFlowId());
entity.setRequirement(model.getRequirement()); entity.setRequirement(model.getRequirement());
AuthenticationFlowEntity flow = em.find(AuthenticationFlowEntity.class, model.getParentFlow()); AuthenticationFlowEntity flow = em.find(AuthenticationFlowEntity.class, model.getParentFlow());
entity.setFlow(flow); entity.setParentFlow(flow);
flow.getExecutions().add(entity); flow.getExecutions().add(entity);
entity.setRealm(realm); entity.setRealm(realm);
entity.setUserSetupAllowed(model.isUserSetupAllowed()); entity.setUserSetupAllowed(model.isUserSetupAllowed());
@ -1648,6 +1653,7 @@ public class RealmAdapter implements RealmModel {
entity.setPriority(model.getPriority()); entity.setPriority(model.getPriority());
entity.setRequirement(model.getRequirement()); entity.setRequirement(model.getRequirement());
entity.setUserSetupAllowed(model.isUserSetupAllowed()); entity.setUserSetupAllowed(model.isUserSetupAllowed());
entity.setFlowId(model.getFlowId());
em.flush(); em.flush();
} }

View file

@ -19,9 +19,9 @@ import javax.persistence.Table;
@Table(name="AUTHENTICATION_EXECUTION") @Table(name="AUTHENTICATION_EXECUTION")
@Entity @Entity
@NamedQueries({ @NamedQueries({
@NamedQuery(name="getAuthenticationExecutionsByFlow", query="select authenticator from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.flow = :flow"), @NamedQuery(name="getAuthenticationExecutionsByFlow", query="select authenticator from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.parentFlow = :parentFlow"),
@NamedQuery(name="deleteAuthenticationExecutionsByRealm", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm"), @NamedQuery(name="deleteAuthenticationExecutionsByRealm", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm"),
@NamedQuery(name="deleteAuthenticationExecutionsByRealmAndFlow", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.flow = :flow"), @NamedQuery(name="deleteAuthenticationExecutionsByRealmAndFlow", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.parentFlow = :parentFlow"),
}) })
public class AuthenticationExecutionEntity { public class AuthenticationExecutionEntity {
@Id @Id
@ -34,11 +34,14 @@ public class AuthenticationExecutionEntity {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "FLOW_ID") @JoinColumn(name = "FLOW_ID")
protected AuthenticationFlowEntity flow; protected AuthenticationFlowEntity parentFlow;
@Column(name="AUTHENTICATOR") @Column(name="AUTHENTICATOR")
protected String authenticator; protected String authenticator;
@Column(name="AUTH_FLOW_ID")
protected String flowId;
@Column(name="REQUIREMENT") @Column(name="REQUIREMENT")
protected AuthenticationExecutionModel.Requirement requirement; protected AuthenticationExecutionModel.Requirement requirement;
@ -107,11 +110,19 @@ public class AuthenticationExecutionEntity {
this.autheticatorFlow = autheticatorFlow; this.autheticatorFlow = autheticatorFlow;
} }
public AuthenticationFlowEntity getFlow() { public AuthenticationFlowEntity getParentFlow() {
return flow; return parentFlow;
} }
public void setFlow(AuthenticationFlowEntity flow) { public void setParentFlow(AuthenticationFlowEntity flow) {
this.flow = flow; this.parentFlow = flow;
}
public String getFlowId() {
return flowId;
}
public void setFlowId(String flowId) {
this.flowId = flowId;
} }
} }

View file

@ -36,10 +36,13 @@ public class AuthenticationFlowEntity {
@Column(name="ALIAS") @Column(name="ALIAS")
protected String alias; protected String alias;
@Column(name="PROVIDER_ID")
protected String providerId;
@Column(name="DESCRIPTION") @Column(name="DESCRIPTION")
protected String description; protected String description;
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "flow") @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "parentFlow")
Collection<AuthenticationExecutionEntity> executions = new ArrayList<AuthenticationExecutionEntity>(); Collection<AuthenticationExecutionEntity> executions = new ArrayList<AuthenticationExecutionEntity>();
public String getId() { public String getId() {
return id; return id;
@ -80,4 +83,12 @@ public class AuthenticationFlowEntity {
public void setExecutions(Collection<AuthenticationExecutionEntity> executions) { public void setExecutions(Collection<AuthenticationExecutionEntity> executions) {
this.executions = executions; this.executions = executions;
} }
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
} }

View file

@ -1341,6 +1341,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
if (toUpdate == null) return; if (toUpdate == null) return;
toUpdate.setAlias(model.getAlias()); toUpdate.setAlias(model.getAlias());
toUpdate.setDescription(model.getDescription()); toUpdate.setDescription(model.getDescription());
toUpdate.setProviderId(model.getProviderId());
updateMongoEntity(); updateMongoEntity();
} }
@ -1350,6 +1351,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setId(KeycloakModelUtils.generateId()); entity.setId(KeycloakModelUtils.generateId());
entity.setAlias(model.getAlias()); entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription()); entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
getMongoEntity().getAuthenticationFlows().add(entity); getMongoEntity().getAuthenticationFlows().add(entity);
model.setId(entity.getId()); model.setId(entity.getId());
updateMongoEntity(); updateMongoEntity();
@ -1378,6 +1380,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
model.setRequirement(entity.getRequirement()); model.setRequirement(entity.getRequirement());
model.setPriority(entity.getPriority()); model.setPriority(entity.getPriority());
model.setAuthenticator(entity.getAuthenticator()); model.setAuthenticator(entity.getAuthenticator());
model.setFlowId(entity.getFlowId());
model.setParentFlow(entity.getParentFlow()); model.setParentFlow(entity.getParentFlow());
model.setAutheticatorFlow(entity.isAuthenticatorFlow()); model.setAutheticatorFlow(entity.isAuthenticatorFlow());
return model; return model;
@ -1410,6 +1413,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setRequirement(model.getRequirement()); entity.setRequirement(model.getRequirement());
entity.setUserSetupAllowed(model.isUserSetupAllowed()); entity.setUserSetupAllowed(model.isUserSetupAllowed());
entity.setAuthenticatorFlow(model.isAutheticatorFlow()); entity.setAuthenticatorFlow(model.isAutheticatorFlow());
entity.setFlowId(model.getFlowId());
entity.setParentFlow(model.getParentFlow()); entity.setParentFlow(model.getParentFlow());
AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow()); AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow());
flow.getExecutions().add(entity); flow.getExecutions().add(entity);
@ -1433,6 +1437,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setAuthenticator(model.getAuthenticator()); entity.setAuthenticator(model.getAuthenticator());
entity.setPriority(model.getPriority()); entity.setPriority(model.getPriority());
entity.setRequirement(model.getRequirement()); entity.setRequirement(model.getRequirement());
entity.setFlowId(model.getFlowId());
entity.setUserSetupAllowed(model.isUserSetupAllowed()); entity.setUserSetupAllowed(model.isUserSetupAllowed());
updateMongoEntity(); updateMongoEntity();
} }

View file

@ -0,0 +1,12 @@
package org.keycloak.authentication;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface AuthenticationFlow {
Response processAction(String actionExecution);
Response processFlow();
}

View file

@ -26,7 +26,6 @@ import org.keycloak.util.Time;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.util.Iterator;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -64,6 +63,7 @@ public class AuthenticationProcessor {
ATTEMPTED ATTEMPTED
} }
public static enum Error { public static enum Error {
EXPIRED_CODE, EXPIRED_CODE,
INVALID_CLIENT_SESSION, INVALID_CLIENT_SESSION,
@ -229,12 +229,14 @@ public class AuthenticationProcessor {
this.challenge = challenge; this.challenge = challenge;
} }
@Override @Override
public void forceChallenge(Response challenge) { public void forceChallenge(Response challenge) {
this.status = Status.FORCE_CHALLENGE; this.status = Status.FORCE_CHALLENGE;
this.challenge = challenge; this.challenge = challenge;
} }
@Override @Override
public void failureChallenge(Error error, Response challenge) { public void failureChallenge(Error error, Response challenge) {
this.error = error; this.error = error;
@ -242,6 +244,7 @@ public class AuthenticationProcessor {
this.challenge = challenge; this.challenge = challenge;
} }
@Override @Override
public void failure(Error error, Response challenge) { public void failure(Error error, Response challenge) {
this.error = error; this.error = error;
@ -264,7 +267,8 @@ public class AuthenticationProcessor {
@Override @Override
public void setUser(UserModel user) { public void setUser(UserModel user) {
UserModel previousUser = getUser(); UserModel previousUser = getUser();
if (previousUser != null && !user.getId().equals(previousUser.getId())) throw new AuthException(Error.USER_CONFLICT); if (previousUser != null && !user.getId().equals(previousUser.getId()))
throw new AuthException(Error.USER_CONFLICT);
validateUser(user); validateUser(user);
getClientSession().setAuthenticatedUser(user); getClientSession().setAuthenticatedUser(user);
} }
@ -423,15 +427,22 @@ public class AuthenticationProcessor {
} }
public FlowExecution createFlowExecution(String flowId) { public AuthenticationFlow createFlowExecution(String flowId, AuthenticationExecutionModel execution) {
AuthenticationFlowModel flow = realm.getAuthenticationFlowById(flowId); AuthenticationFlowModel flow = realm.getAuthenticationFlowById(flowId);
if (flow == null) { if (flow == null) {
logger.error("Unknown flow to execute with"); logger.error("Unknown flow to execute with");
throw new AuthException(Error.INTERNAL_ERROR); throw new AuthException(Error.INTERNAL_ERROR);
} }
FlowExecution flowExecution = new FlowExecution(); if (flow.getProviderId() == null || flow.getProviderId().equals("basic-flow")) {
DefaultAuthenticationFlow flowExecution = new DefaultAuthenticationFlow(this);
flowExecution.executions = realm.getAuthenticationExecutions(flow.getId()).iterator(); flowExecution.executions = realm.getAuthenticationExecutions(flow.getId()).iterator();
return flowExecution; return flowExecution;
} else if (flow.getProviderId().equals("form-flow")) {
FormAuthenticationFlow flowExecution = new FormAuthenticationFlow(this, execution);
return flowExecution;
}
throw new AuthException("Unknown flow provider type", Error.INTERNAL_ERROR);
} }
public Response authenticate() throws AuthException { public Response authenticate() throws AuthException {
@ -447,8 +458,8 @@ public class AuthenticationProcessor {
} }
UserModel authUser = clientSession.getAuthenticatedUser(); UserModel authUser = clientSession.getAuthenticatedUser();
validateUser(authUser); validateUser(authUser);
FlowExecution flowExecution = createFlowExecution(this.flowId); AuthenticationFlow authenticationFlow = createFlowExecution(this.flowId, null);
Response challenge = flowExecution.processFlow(); Response challenge = authenticationFlow.processFlow();
if (challenge != null) return challenge; if (challenge != null) return challenge;
if (clientSession.getAuthenticatedUser() == null) { if (clientSession.getAuthenticatedUser() == null) {
throw new AuthException(Error.UNKNOWN_USER); throw new AuthException(Error.UNKNOWN_USER);
@ -488,8 +499,8 @@ public class AuthenticationProcessor {
event.detail(Details.AUTH_TYPE, authType); event.detail(Details.AUTH_TYPE, authType);
} }
FlowExecution flowExecution = createFlowExecution(this.flowId); AuthenticationFlow authenticationFlow = createFlowExecution(this.flowId, model);
Response challenge = flowExecution.action(execution); Response challenge = authenticationFlow.processAction(execution);
if (challenge != null) return challenge; if (challenge != null) return challenge;
if (clientSession.getAuthenticatedUser() == null) { if (clientSession.getAuthenticatedUser() == null) {
throw new AuthException(Error.UNKNOWN_USER); throw new AuthException(Error.UNKNOWN_USER);
@ -520,8 +531,8 @@ public class AuthenticationProcessor {
} }
UserModel authUser = clientSession.getAuthenticatedUser(); UserModel authUser = clientSession.getAuthenticatedUser();
validateUser(authUser); validateUser(authUser);
FlowExecution flowExecution = createFlowExecution(this.flowId); AuthenticationFlow authenticationFlow = createFlowExecution(this.flowId, null);
Response challenge = flowExecution.processFlow(); Response challenge = authenticationFlow.processFlow();
if (challenge != null) return challenge; if (challenge != null) return challenge;
String username = clientSession.getAuthenticatedUser().getUsername(); String username = clientSession.getAuthenticatedUser().getUsername();
@ -576,182 +587,9 @@ public class AuthenticationProcessor {
} }
class FlowExecution { public AuthenticatorContext createAuthenticatorContext(AuthenticationExecutionModel model, Authenticator authenticator) {
Response alternativeChallenge = null; return new Result(model, authenticator);
AuthenticationExecutionModel challengedAlternativeExecution = null;
boolean alternativeSuccessful = false;
Iterator<AuthenticationExecutionModel> executions;
protected boolean isProcessed(AuthenticationExecutionModel model) {
if (model.isDisabled()) return true;
ClientSessionModel.ExecutionStatus status = clientSession.getExecutionStatus().get(model.getId());
if (status == null) return false;
return status == ClientSessionModel.ExecutionStatus.SUCCESS || status == ClientSessionModel.ExecutionStatus.SKIPPED
|| status == ClientSessionModel.ExecutionStatus.ATTEMPTED
|| status == ClientSessionModel.ExecutionStatus.SETUP_REQUIRED;
}
public Response action(String actionExecution) {
while (executions.hasNext()) {
AuthenticationExecutionModel model = executions.next();
if (isProcessed(model)) {
logger.debug("execution is processed");
if (!alternativeSuccessful && model.isAlternative() && isSuccessful(model)) alternativeSuccessful = true;
continue;
}
if (!model.getId().equals(actionExecution)) {
if (model.isAutheticatorFlow()) {
FlowExecution flowExecution = createFlowExecution(model.getAuthenticator());
return flowExecution.action(actionExecution);
} else {
throw new AuthException("action is not current execution", Error.INTERNAL_ERROR);
}
} else { // we found the action
AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
Authenticator authenticator = factory.create();
Result result = new Result(model, authenticator);
authenticator.action(result);
Response response = processResult(result);
if (response == null) return processFlow();
else return response;
}
}
throw new AuthException("action is not in current execution", Error.INTERNAL_ERROR);
}
public Response processFlow() {
while (executions.hasNext()) {
AuthenticationExecutionModel model = executions.next();
if (isProcessed(model)) {
logger.debug("execution is processed");
if (!alternativeSuccessful && model.isAlternative() && isSuccessful(model)) alternativeSuccessful = true;
continue;
}
if (model.isAlternative() && alternativeSuccessful) {
clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
}
if (model.isAutheticatorFlow()) {
FlowExecution flowExecution = createFlowExecution(model.getAuthenticator());
Response flowResponse = flowExecution.processFlow();
if (flowResponse == null) {
clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
if (model.isAlternative()) alternativeSuccessful = true;
continue;
} else {
return flowResponse;
}
}
AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
Authenticator authenticator = factory.create();
logger.debugv("authenticator: {0}", factory.getId());
UserModel authUser = clientSession.getAuthenticatedUser();
if (authenticator.requiresUser() && authUser == null){
if (alternativeChallenge != null) {
clientSession.setExecutionStatus(challengedAlternativeExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return alternativeChallenge;
}
throw new AuthException("authenticator: " + factory.getId(), Error.UNKNOWN_USER);
}
boolean configuredFor = false;
if (authenticator.requiresUser() && authUser != null) {
configuredFor = authenticator.configuredFor(session, realm, authUser);
if (!configuredFor) {
if (model.isRequired()) {
if (model.isUserSetupAllowed()) {
logger.debugv("authenticator SETUP_REQUIRED: {0}", factory.getId());
clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
authenticator.setRequiredActions(session, realm, clientSession.getAuthenticatedUser());
continue;
} else {
throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED);
}
} else if (model.isOptional()) {
clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
}
}
}
Result context = new Result(model, authenticator);
authenticator.authenticate(context);
Response response = processResult(context);
if (response != null) return response;
}
return null;
}
public Response processResult(AuthenticatorContext result) {
AuthenticationExecutionModel execution = result.getExecution();
Status status = result.getStatus();
if (status == Status.SUCCESS){
logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
if (execution.isAlternative()) alternativeSuccessful = true;
return null;
} else if (status == Status.FAILED) {
logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
logFailure();
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
if (result.getChallenge() != null) {
return sendChallenge(result, execution);
}
throw new AuthException(result.getError());
} else if (status == Status.FORCE_CHALLENGE) {
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
} else if (status == Status.CHALLENGE) {
logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
if (execution.isRequired()) {
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
UserModel authenticatedUser = clientSession.getAuthenticatedUser();
if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(session, realm, authenticatedUser)) {
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
if (execution.isAlternative()) {
alternativeChallenge = result.getChallenge();
challengedAlternativeExecution = execution;
} else {
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
}
return null;
} else if (status == Status.FAILURE_CHALLENGE) {
logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
logFailure();
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
} else if (status == Status.ATTEMPTED) {
logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
throw new AuthException(Error.INVALID_CREDENTIALS);
}
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
return null;
} else {
logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
logger.error("Unknown result status");
throw new AuthException(Error.INTERNAL_ERROR);
}
}
public Response sendChallenge(AuthenticatorContext result, AuthenticationExecutionModel execution) {
clientSession.setNote(CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
return result.getChallenge();
} }
}
} }

View file

@ -10,8 +10,8 @@ import org.keycloak.provider.Provider;
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public interface Authenticator extends Provider { public interface Authenticator extends Provider {
boolean requiresUser();
void authenticate(AuthenticatorContext context); void authenticate(AuthenticatorContext context);
boolean requiresUser();
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user); boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
/** /**

View file

@ -24,7 +24,7 @@ public class AuthenticatorUtil {
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) { for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
executions.add(model); executions.add(model);
if (model.isAutheticatorFlow() && model.isEnabled()) { if (model.isAutheticatorFlow() && model.isEnabled()) {
recurseExecutions(realm, model.getAuthenticator(), executions); recurseExecutions(realm, model.getFlowId(), executions);
} }
} }
} }
@ -32,7 +32,7 @@ public class AuthenticatorUtil {
public static AuthenticationExecutionModel findExecutionByAuthenticator(RealmModel realm, String flowId, String authProviderId) { public static AuthenticationExecutionModel findExecutionByAuthenticator(RealmModel realm, String flowId, String authProviderId) {
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) { for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
if (model.isAutheticatorFlow()) { if (model.isAutheticatorFlow()) {
AuthenticationExecutionModel recurse = findExecutionByAuthenticator(realm, model.getAuthenticator(), authProviderId); AuthenticationExecutionModel recurse = findExecutionByAuthenticator(realm, model.getFlowId(), authProviderId);
if (recurse != null) return recurse; if (recurse != null) return recurse;
} }

View file

@ -0,0 +1,207 @@
package org.keycloak.authentication;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserModel;
import javax.ws.rs.core.Response;
import java.util.Iterator;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class DefaultAuthenticationFlow implements AuthenticationFlow {
Response alternativeChallenge = null;
AuthenticationExecutionModel challengedAlternativeExecution = null;
boolean alternativeSuccessful = false;
Iterator<AuthenticationExecutionModel> executions;
AuthenticationProcessor processor;
public DefaultAuthenticationFlow(AuthenticationProcessor processor) {
this.processor = processor;
}
protected boolean isProcessed(AuthenticationExecutionModel model) {
if (model.isDisabled()) return true;
ClientSessionModel.ExecutionStatus status = processor.getClientSession().getExecutionStatus().get(model.getId());
if (status == null) return false;
return status == ClientSessionModel.ExecutionStatus.SUCCESS || status == ClientSessionModel.ExecutionStatus.SKIPPED
|| status == ClientSessionModel.ExecutionStatus.ATTEMPTED
|| status == ClientSessionModel.ExecutionStatus.SETUP_REQUIRED;
}
@Override
public Response processAction(String actionExecution) {
while (executions.hasNext()) {
AuthenticationExecutionModel model = executions.next();
if (isProcessed(model)) {
AuthenticationProcessor.logger.debug("execution is processed");
if (!alternativeSuccessful && model.isAlternative() && processor.isSuccessful(model))
alternativeSuccessful = true;
continue;
}
if (!model.getId().equals(actionExecution)) {
if (model.isAutheticatorFlow()) {
AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
return authenticationFlow.processAction(actionExecution);
} else {
throw new AuthenticationProcessor.AuthException("action is not current execution", AuthenticationProcessor.Error.INTERNAL_ERROR);
}
} else { // we found the action
AuthenticatorFactory factory = (AuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
Authenticator authenticator = factory.create();
AuthenticatorContext result = processor.createAuthenticatorContext(model, authenticator);
authenticator.action(result);
Response response = processResult(result);
if (response == null) return processFlow();
else return response;
}
}
throw new AuthenticationProcessor.AuthException("action is not in current execution", AuthenticationProcessor.Error.INTERNAL_ERROR);
}
@Override
public Response processFlow() {
while (executions.hasNext()) {
AuthenticationExecutionModel model = executions.next();
if (isProcessed(model)) {
AuthenticationProcessor.logger.debug("execution is processed");
if (!alternativeSuccessful && model.isAlternative() && processor.isSuccessful(model))
alternativeSuccessful = true;
continue;
}
if (model.isAlternative() && alternativeSuccessful) {
processor.getClientSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
}
if (model.isAutheticatorFlow()) {
AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
Response flowChallenge = authenticationFlow.processFlow();
if (flowChallenge == null) {
processor.getClientSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
if (model.isAlternative()) alternativeSuccessful = true;
continue;
} else {
if (model.isAlternative()) {
alternativeChallenge = flowChallenge;
challengedAlternativeExecution = model;
} else if (model.isRequired()) {
processor.getClientSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return flowChallenge;
} else if (model.isOptional()) {
processor.getClientSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
} else {
processor.getClientSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
}
return flowChallenge;
}
}
AuthenticatorFactory factory = (AuthenticatorFactory) processor.getSession().getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
Authenticator authenticator = factory.create();
AuthenticationProcessor.logger.debugv("authenticator: {0}", factory.getId());
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
if (authenticator.requiresUser() && authUser == null) {
if (alternativeChallenge != null) {
processor.getClientSession().setExecutionStatus(challengedAlternativeExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return alternativeChallenge;
}
throw new AuthenticationProcessor.AuthException("authenticator: " + factory.getId(), AuthenticationProcessor.Error.UNKNOWN_USER);
}
boolean configuredFor = false;
if (authenticator.requiresUser() && authUser != null) {
configuredFor = authenticator.configuredFor(processor.getSession(), processor.getRealm(), authUser);
if (!configuredFor) {
if (model.isRequired()) {
if (model.isUserSetupAllowed()) {
AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}", factory.getId());
processor.getClientSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
authenticator.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getClientSession().getAuthenticatedUser());
continue;
} else {
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.CREDENTIAL_SETUP_REQUIRED);
}
} else if (model.isOptional()) {
processor.getClientSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
}
}
}
AuthenticatorContext context = processor.createAuthenticatorContext(model, authenticator);
authenticator.authenticate(context);
Response response = processResult(context);
if (response != null) return response;
}
return null;
}
public Response processResult(AuthenticatorContext result) {
AuthenticationExecutionModel execution = result.getExecution();
AuthenticationProcessor.Status status = result.getStatus();
if (status == AuthenticationProcessor.Status.SUCCESS) {
AuthenticationProcessor.logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
if (execution.isAlternative()) alternativeSuccessful = true;
return null;
} else if (status == AuthenticationProcessor.Status.FAILED) {
AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
processor.logFailure();
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
if (result.getChallenge() != null) {
return sendChallenge(result, execution);
}
throw new AuthenticationProcessor.AuthException(result.getError());
} else if (status == AuthenticationProcessor.Status.FORCE_CHALLENGE) {
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
} else if (status == AuthenticationProcessor.Status.CHALLENGE) {
AuthenticationProcessor.logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
if (execution.isRequired()) {
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
UserModel authenticatedUser = processor.getClientSession().getAuthenticatedUser();
if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
if (execution.isAlternative()) {
alternativeChallenge = result.getChallenge();
challengedAlternativeExecution = execution;
} else {
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
}
return null;
} else if (status == AuthenticationProcessor.Status.FAILURE_CHALLENGE) {
AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
processor.logFailure();
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
} else if (status == AuthenticationProcessor.Status.ATTEMPTED) {
AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INVALID_CREDENTIALS);
}
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
return null;
} else {
AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
AuthenticationProcessor.logger.error("Unknown result status");
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INTERNAL_ERROR);
}
}
public Response sendChallenge(AuthenticatorContext result, AuthenticationExecutionModel execution) {
processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
return result.getChallenge();
}
}

View file

@ -0,0 +1,24 @@
package org.keycloak.authentication;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface FormAction extends Provider {
void authenticate(FormContext context);
boolean requiresUser();
boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user);
/**
* Set actions to configure authenticator
*
*/
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
}

View file

@ -0,0 +1,10 @@
package org.keycloak.authentication;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface FormActionFactory extends ProviderFactory<FormAction> {
}

View file

@ -0,0 +1,32 @@
package org.keycloak.authentication;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FormActionSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "form-action";
}
@Override
public Class<? extends Provider> getProviderClass() {
return FormAction.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return FormActionFactory.class;
}
}

View file

@ -0,0 +1,290 @@
package org.keycloak.authentication;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.services.managers.BruteForceProtector;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.util.Iterator;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class FormAuthenticationFlow implements AuthenticationFlow {
AuthenticationProcessor processor;
AuthenticationExecutionModel execution;
public FormAuthenticationFlow(AuthenticationProcessor processor, AuthenticationExecutionModel execution) {
this.processor = processor;
this.execution = execution;
}
private static class FormActionResult implements FormContext {
AuthenticatorContext delegate;
FormAuthenticator authenticator;
FormActionResult(AuthenticatorContext delegate, FormAuthenticator authenticator) {
this.delegate = delegate;
this.authenticator = authenticator;
}
@Override
public FormAuthenticator getFormAuthenticator() {
return authenticator;
}
@Override
public EventBuilder getEvent() {
return delegate.getEvent();
}
@Override
public AuthenticationExecutionModel getExecution() {
return delegate.getExecution();
}
@Override
public void setExecution(AuthenticationExecutionModel execution) {
delegate.setExecution(execution);
}
@Override
public AuthenticatorConfigModel getAuthenticatorConfig() {
return delegate.getAuthenticatorConfig();
}
@Override
public String getAction() {
return delegate.getAction();
}
@Override
public Authenticator getAuthenticator() {
return delegate.getAuthenticator();
}
@Override
public void setAuthenticator(Authenticator authenticator) {
delegate.setAuthenticator(authenticator);
}
@Override
public AuthenticationProcessor.Status getStatus() {
return delegate.getStatus();
}
@Override
public UserModel getUser() {
return delegate.getUser();
}
@Override
public void setUser(UserModel user) {
delegate.setUser(user);
}
@Override
public RealmModel getRealm() {
return delegate.getRealm();
}
@Override
public ClientSessionModel getClientSession() {
return delegate.getClientSession();
}
@Override
public void attachUserSession(UserSessionModel userSession) {
delegate.attachUserSession(userSession);
}
@Override
public ClientConnection getConnection() {
return delegate.getConnection();
}
@Override
public UriInfo getUriInfo() {
return delegate.getUriInfo();
}
@Override
public KeycloakSession getSession() {
return delegate.getSession();
}
@Override
public HttpRequest getHttpRequest() {
return delegate.getHttpRequest();
}
@Override
public BruteForceProtector getProtector() {
return delegate.getProtector();
}
@Override
public void success() {
delegate.success();
}
@Override
public void failure(AuthenticationProcessor.Error error) {
delegate.failure(error);
}
@Override
public void failure(AuthenticationProcessor.Error error, Response response) {
delegate.failure(error, response);
}
@Override
public void challenge(Response challenge) {
delegate.challenge(challenge);
}
@Override
public void forceChallenge(Response challenge) {
delegate.forceChallenge(challenge);
}
@Override
public void failureChallenge(AuthenticationProcessor.Error error, Response challenge) {
delegate.failureChallenge(error, challenge);
}
@Override
public void attempted() {
delegate.attempted();
}
@Override
public String getForwardedErrorMessage() {
return delegate.getForwardedErrorMessage();
}
@Override
public String generateAccessCode() {
return delegate.generateAccessCode();
}
@Override
public Response getChallenge() {
return delegate.getChallenge();
}
@Override
public AuthenticationProcessor.Error getError() {
return delegate.getError();
}
}
@Override
public Response processAction(String actionExecution) {
if (!actionExecution.equals(execution.getId())) {
throw new AuthenticationProcessor.AuthException("action is not current execution", AuthenticationProcessor.Error.INTERNAL_ERROR);
}
FormAuthenticator authenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator());
for (AuthenticationExecutionModel formActionExecution : processor.getRealm().getAuthenticationExecutions(execution.getFlowId())) {
FormAction action = processor.getSession().getProvider(FormAction.class, execution.getAuthenticator());
UserModel authUser = processor.getClientSession().getAuthenticatedUser();
if (action.requiresUser() && authUser == null) {
throw new AuthenticationProcessor.AuthException("form action: " + execution.getAuthenticator() + " requires user", AuthenticationProcessor.Error.UNKNOWN_USER);
}
boolean configuredFor = false;
if (action.requiresUser() && authUser != null) {
configuredFor = action.configuredFor(processor.getSession(), processor.getRealm(), authUser);
if (!configuredFor) {
if (formActionExecution.isRequired()) {
if (formActionExecution.isUserSetupAllowed()) {
AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}", execution.getAuthenticator());
processor.getClientSession().setExecutionStatus(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
action.setRequiredActions(processor.getSession(), processor.getRealm(), authUser);
continue;
} else {
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.CREDENTIAL_SETUP_REQUIRED);
}
} else if (formActionExecution.isOptional()) {
processor.getClientSession().setExecutionStatus(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
}
}
}
FormActionResult result = new FormActionResult(processor.createAuthenticatorContext(formActionExecution, null), authenticator);
action.authenticate(result);
return processResult(result, formActionExecution);
}
return null;
}
@Override
public Response processFlow() {
FormAuthenticator authenticator = processor.getSession().getProvider(FormAuthenticator.class, execution.getAuthenticator());
AuthenticatorContext context = processor.createAuthenticatorContext(execution, null);
authenticator.authenticate(context);
return processResult(context, execution);
}
public Response processResult(AuthenticatorContext result, AuthenticationExecutionModel execution) {
AuthenticationProcessor.Status status = result.getStatus();
if (status == AuthenticationProcessor.Status.SUCCESS) {
return null;
} else if (status == AuthenticationProcessor.Status.FAILED) {
AuthenticationProcessor.logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
processor.logFailure();
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
if (result.getChallenge() != null) {
return sendChallenge(result, execution);
}
throw new AuthenticationProcessor.AuthException(result.getError());
} else if (status == AuthenticationProcessor.Status.FORCE_CHALLENGE) {
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
} else if (status == AuthenticationProcessor.Status.CHALLENGE) {
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
} else if (status == AuthenticationProcessor.Status.FAILURE_CHALLENGE) {
AuthenticationProcessor.logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
processor.logFailure();
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
} else if (status == AuthenticationProcessor.Status.ATTEMPTED) {
AuthenticationProcessor.logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INVALID_CREDENTIALS);
}
processor.getClientSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
return null;
} else {
AuthenticationProcessor.logger.debugv("authenticator INTERNAL_ERROR: {0}", execution.getAuthenticator());
AuthenticationProcessor.logger.error("Unknown result status");
throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.INTERNAL_ERROR);
}
}
public Response sendChallenge(AuthenticatorContext result, AuthenticationExecutionModel execution) {
processor.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
return result.getChallenge();
}
}

View file

@ -0,0 +1,14 @@
package org.keycloak.authentication;
import org.keycloak.provider.Provider;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface FormAuthenticator extends Provider {
void authenticate(AuthenticatorContext context);
Response createChallenge(FormContext context, String... errorMessages);
}

View file

@ -0,0 +1,10 @@
package org.keycloak.authentication;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface FormAuthenticatorFactory extends ProviderFactory<FormAuthenticator> {
}

View file

@ -0,0 +1,32 @@
package org.keycloak.authentication;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class FormAuthenticatorSpi implements Spi {
@Override
public boolean isInternal() {
return false;
}
@Override
public String getName() {
return "form-authenticator";
}
@Override
public Class<? extends Provider> getProviderClass() {
return FormAuthenticator.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return FormAuthenticatorFactory.class;
}
}

View file

@ -0,0 +1,9 @@
package org.keycloak.authentication;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface FormContext extends AuthenticatorContext {
FormAuthenticator getFormAuthenticator();
}

View file

@ -1,4 +1,4 @@
package org.keycloak.authentication.actions; package org.keycloak.authentication.requiredactions;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.authentication.RequiredActionContext; import org.keycloak.authentication.RequiredActionContext;

View file

@ -1,4 +1,4 @@
package org.keycloak.authentication.actions; package org.keycloak.authentication.requiredactions;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
@ -6,13 +6,11 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.util.Time; import org.keycloak.util.Time;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;

View file

@ -1,4 +1,4 @@
package org.keycloak.authentication.actions; package org.keycloak.authentication.requiredactions;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
@ -6,13 +6,9 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.ClientSessionCode;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;

View file

@ -1,4 +1,4 @@
package org.keycloak.authentication.actions; package org.keycloak.authentication.requiredactions;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
@ -6,19 +6,13 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.util.Time;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.concurrent.TimeUnit;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,4 +1,4 @@
package org.keycloak.authentication.actions; package org.keycloak.authentication.requiredactions;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
@ -8,13 +8,11 @@ import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.validation.Validation; import org.keycloak.services.validation.Validation;
import org.keycloak.util.Time; import org.keycloak.util.Time;

View file

@ -124,7 +124,7 @@ public class AuthenticationManagementResource {
rep.setSubFlow(false); rep.setSubFlow(false);
rep.setRequirementChoices(new LinkedList<String>()); rep.setRequirementChoices(new LinkedList<String>());
if (execution.isAutheticatorFlow()) { if (execution.isAutheticatorFlow()) {
AuthenticationFlowModel flowRef = realm.getAuthenticationFlowById(execution.getAuthenticator()); AuthenticationFlowModel flowRef = realm.getAuthenticationFlowById(execution.getFlowId());
rep.setReferenceType(flowRef.getAlias()); rep.setReferenceType(flowRef.getAlias());
rep.setExecution(execution.getId()); rep.setExecution(execution.getId());
rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name()); rep.getRequirementChoices().add(AuthenticationExecutionModel.Requirement.ALTERNATIVE.name());

View file

@ -1,5 +1,5 @@
org.keycloak.authentication.actions.UpdatePassword org.keycloak.authentication.requiredactions.UpdatePassword
org.keycloak.authentication.actions.UpdateProfile org.keycloak.authentication.requiredactions.UpdateProfile
org.keycloak.authentication.actions.UpdateTotp org.keycloak.authentication.requiredactions.UpdateTotp
org.keycloak.authentication.actions.VerifyEmail org.keycloak.authentication.requiredactions.VerifyEmail
org.keycloak.authentication.actions.TermsAndConditions org.keycloak.authentication.requiredactions.TermsAndConditions

View file

@ -5,3 +5,5 @@ org.keycloak.wellknown.WellKnownSpi
org.keycloak.messages.MessagesSpi org.keycloak.messages.MessagesSpi
org.keycloak.authentication.AuthenticatorSpi org.keycloak.authentication.AuthenticatorSpi
org.keycloak.authentication.RequiredActionSpi org.keycloak.authentication.RequiredActionSpi
org.keycloak.authentication.FormAuthenticatorSpi
org.keycloak.authentication.FormActionSpi