Merge pull request #1413 from patriot1burke/master

refactor auth spi into additional form spi
This commit is contained in:
Bill Burke 2015-06-24 21:29:26 -04:00
commit 2e5704c6b4
39 changed files with 847 additions and 235 deletions

View file

@ -12,6 +12,16 @@
<constraints nullable="true"/>
</column>
</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"/>
<renameTable oldTableName="AUTHENTICATOR_CONFIG" newTableName="AUTHENTICATOR_CONFIG_ENTRY"/>
<renameTable oldTableName="AUTHENTICATOR" newTableName="AUTHENTICATOR_CONFIG"/>

View file

@ -22,6 +22,7 @@ public class AuthenticationExecutionModel implements Serializable {
private String id;
private String authenticatorConfig;
private String authenticator;
private String flowId;
private boolean autheticatorFlow;
private Requirement requirement;
private boolean userSetupAllowed;
@ -84,6 +85,19 @@ public class AuthenticationExecutionModel implements Serializable {
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?
*

View file

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

View file

@ -51,6 +51,7 @@ public interface ClientSessionModel {
public String getNote(String name);
public void setNote(String name, String value);
public void removeNote(String name);
public Map<String, String> getNotes();
/**
* These are notes you want applied to the UserSessionModel when the client session is attached to it.

View file

@ -10,6 +10,7 @@ import org.keycloak.models.AuthenticationExecutionModel;
public class AuthenticationExecutionEntity {
protected String id;
protected String authenticator;
protected String flowId;
protected AuthenticationExecutionModel.Requirement requirement;
protected int priority;
private boolean userSetupAllowed;
@ -71,4 +72,12 @@ public class AuthenticationExecutionEntity {
public void setParentFlow(String 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 alias;
protected String description;
protected String providerId;
List<AuthenticationExecutionEntity> executions = new ArrayList<AuthenticationExecutionEntity>();
public String getId() {
return id;
@ -44,4 +46,12 @@ public class AuthenticationFlowEntity {
public void setExecutions(List<AuthenticationExecutionEntity> 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();
browser.setAlias(BROWSER_FLOW);
browser.setDescription("browser based authentication");
browser.setProviderId("basic-flow");
browser = realm.addAuthenticationFlow(browser);
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId());
@ -40,11 +41,12 @@ public class DefaultAuthenticationFlows {
AuthenticationFlowModel forms = new AuthenticationFlowModel();
forms.setAlias(FORMS_FLOW);
forms.setDescription("Username, password, otp and other auth forms.");
forms.setProviderId("basic-flow");
forms = realm.addAuthenticationFlow(forms);
execution = new AuthenticationExecutionModel();
execution.setParentFlow(browser.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
execution.setAuthenticator(forms.getId());
execution.setFlowId(forms.getId());
execution.setPriority(30);
execution.setUserSetupAllowed(false);
execution.setAutheticatorFlow(true);

View file

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

View file

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

View file

@ -19,9 +19,9 @@ import javax.persistence.Table;
@Table(name="AUTHENTICATION_EXECUTION")
@Entity
@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="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 {
@Id
@ -34,11 +34,14 @@ public class AuthenticationExecutionEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "FLOW_ID")
protected AuthenticationFlowEntity flow;
protected AuthenticationFlowEntity parentFlow;
@Column(name="AUTHENTICATOR")
protected String authenticator;
@Column(name="AUTH_FLOW_ID")
protected String flowId;
@Column(name="REQUIREMENT")
protected AuthenticationExecutionModel.Requirement requirement;
@ -107,11 +110,19 @@ public class AuthenticationExecutionEntity {
this.autheticatorFlow = autheticatorFlow;
}
public AuthenticationFlowEntity getFlow() {
return flow;
public AuthenticationFlowEntity getParentFlow() {
return parentFlow;
}
public void setFlow(AuthenticationFlowEntity flow) {
this.flow = flow;
public void setParentFlow(AuthenticationFlowEntity 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")
protected String alias;
@Column(name="PROVIDER_ID")
protected String providerId;
@Column(name="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>();
public String getId() {
return id;
@ -80,4 +83,12 @@ public class AuthenticationFlowEntity {
public void setExecutions(Collection<AuthenticationExecutionEntity> 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;
toUpdate.setAlias(model.getAlias());
toUpdate.setDescription(model.getDescription());
toUpdate.setProviderId(model.getProviderId());
updateMongoEntity();
}
@ -1350,6 +1351,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setId(KeycloakModelUtils.generateId());
entity.setAlias(model.getAlias());
entity.setDescription(model.getDescription());
entity.setProviderId(model.getProviderId());
getMongoEntity().getAuthenticationFlows().add(entity);
model.setId(entity.getId());
updateMongoEntity();
@ -1378,6 +1380,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
model.setRequirement(entity.getRequirement());
model.setPriority(entity.getPriority());
model.setAuthenticator(entity.getAuthenticator());
model.setFlowId(entity.getFlowId());
model.setParentFlow(entity.getParentFlow());
model.setAutheticatorFlow(entity.isAuthenticatorFlow());
return model;
@ -1410,6 +1413,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setRequirement(model.getRequirement());
entity.setUserSetupAllowed(model.isUserSetupAllowed());
entity.setAuthenticatorFlow(model.isAutheticatorFlow());
entity.setFlowId(model.getFlowId());
entity.setParentFlow(model.getParentFlow());
AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow());
flow.getExecutions().add(entity);
@ -1433,6 +1437,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
entity.setAuthenticator(model.getAuthenticator());
entity.setPriority(model.getPriority());
entity.setRequirement(model.getRequirement());
entity.setFlowId(model.getFlowId());
entity.setUserSetupAllowed(model.isUserSetupAllowed());
updateMongoEntity();
}

View file

@ -165,6 +165,14 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
}
@Override
public Map<String, String> getNotes() {
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getNotes());
return copy;
}
@Override
public void setUserSessionNote(String name, String value) {
if (entity.getUserSessionNotes() == null) {

View file

@ -81,6 +81,16 @@ public class ClientSessionAdapter implements ClientSessionModel {
return null;
}
@Override
public Map<String, String> getNotes() {
Map<String, String> copy = new HashMap<>();
for (ClientSessionNoteEntity attr : entity.getNotes()) {
copy.put(attr.getName(), attr.getValue());
}
return copy;
}
@Override
public void setUserSessionNote(String name, String value) {
for (ClientUserSessionNoteEntity attr : entity.getUserSessionNotes()) {

View file

@ -9,6 +9,8 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -136,6 +138,14 @@ public class ClientSessionAdapter implements ClientSessionModel {
}
@Override
public Map<String, String> getNotes() {
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getNotes());
return copy;
}
@Override
public void setUserSessionNote(String name, String value) {
entity.getUserSessionNotes().put(name, value);

View file

@ -10,6 +10,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@ -157,6 +158,15 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
updateMongoEntity();
}
@Override
public Map<String, String> getNotes() {
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getNotes());
return copy;
}
@Override
public void setUserSessionNote(String name, String value) {
entity.getUserSessionNotes().put(name, value);

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.UriInfo;
import java.util.Iterator;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -64,6 +63,7 @@ public class AuthenticationProcessor {
ATTEMPTED
}
public static enum Error {
EXPIRED_CODE,
INVALID_CLIENT_SESSION,
@ -229,12 +229,14 @@ public class AuthenticationProcessor {
this.challenge = challenge;
}
@Override
public void forceChallenge(Response challenge) {
this.status = Status.FORCE_CHALLENGE;
this.challenge = challenge;
}
@Override
public void failureChallenge(Error error, Response challenge) {
this.error = error;
@ -242,6 +244,7 @@ public class AuthenticationProcessor {
this.challenge = challenge;
}
@Override
public void failure(Error error, Response challenge) {
this.error = error;
@ -264,7 +267,8 @@ public class AuthenticationProcessor {
@Override
public void setUser(UserModel user) {
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);
getClientSession().setAuthenticatedUser(user);
}
@ -325,6 +329,16 @@ public class AuthenticationProcessor {
clientSession.setTimestamp(Time.currentTime());
return accessCode.getCode();
}
@Override
public Response getChallenge() {
return challenge;
}
@Override
public Error getError() {
return error;
}
}
public static class AuthException extends RuntimeException {
@ -372,15 +386,6 @@ public class AuthenticationProcessor {
}
}
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 boolean isSuccessful(AuthenticationExecutionModel model) {
ClientSessionModel.ExecutionStatus status = clientSession.getExecutionStatus().get(model.getId());
if (status == null) return false;
@ -389,7 +394,7 @@ public class AuthenticationProcessor {
public Response handleBrowserException(Exception failure) {
if (failure instanceof AuthException) {
AuthException e = (AuthException)failure;
AuthException e = (AuthException) failure;
logger.error("failed authentication: " + e.getError().toString(), e);
if (e.getError() == AuthenticationProcessor.Error.INVALID_USER) {
event.error(Errors.USER_NOT_FOUND);
@ -405,11 +410,11 @@ public class AuthenticationProcessor {
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE);
} else if (e.getError() == Error.EXPIRED_CODE) {
} else if (e.getError() == Error.EXPIRED_CODE) {
event.error(Errors.EXPIRED_CODE);
return ErrorPage.error(session, Messages.EXPIRED_CODE);
}else {
} else {
event.error(Errors.INVALID_USER_CREDENTIALS);
return ErrorPage.error(session, Messages.INVALID_USER);
}
@ -422,15 +427,22 @@ public class AuthenticationProcessor {
}
public FlowExecution createFlowExecution(String flowId) {
public AuthenticationFlow createFlowExecution(String flowId, AuthenticationExecutionModel execution) {
AuthenticationFlowModel flow = realm.getAuthenticationFlowById(flowId);
if (flow == null) {
logger.error("Unknown flow to execute with");
throw new AuthException(Error.INTERNAL_ERROR);
}
FlowExecution flowExecution = new FlowExecution();
flowExecution.executions = realm.getAuthenticationExecutions(flow.getId()).iterator();
return flowExecution;
if (flow.getProviderId() == null || flow.getProviderId().equals("basic-flow")) {
DefaultAuthenticationFlow flowExecution = new DefaultAuthenticationFlow(this);
flowExecution.executions = realm.getAuthenticationExecutions(flow.getId()).iterator();
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 {
@ -446,8 +458,8 @@ public class AuthenticationProcessor {
}
UserModel authUser = clientSession.getAuthenticatedUser();
validateUser(authUser);
FlowExecution flowExecution = createFlowExecution(this.flowId);
Response challenge = flowExecution.processFlow();
AuthenticationFlow authenticationFlow = createFlowExecution(this.flowId, null);
Response challenge = authenticationFlow.processFlow();
if (challenge != null) return challenge;
if (clientSession.getAuthenticatedUser() == null) {
throw new AuthException(Error.UNKNOWN_USER);
@ -455,7 +467,7 @@ public class AuthenticationProcessor {
return authenticationComplete();
}
public static void resetFlow(ClientSessionModel clientSession) {
public static void resetFlow(ClientSessionModel clientSession) {
clientSession.setAuthenticatedUser(null);
clientSession.clearExecutionStatus();
clientSession.clearUserSessionNotes();
@ -486,13 +498,9 @@ public class AuthenticationProcessor {
if (authType != null) {
event.detail(Details.AUTH_TYPE, authType);
}
AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getAuthenticator());
Authenticator authenticator = factory.create();
Result context = new Result(model, authenticator);
authenticator.action(context);
FlowExecution flowExecution = createFlowExecution(this.flowId);
Response challenge = flowExecution.action(execution, context);
AuthenticationFlow authenticationFlow = createFlowExecution(this.flowId, model);
Response challenge = authenticationFlow.processAction(execution);
if (challenge != null) return challenge;
if (clientSession.getAuthenticatedUser() == null) {
throw new AuthException(Error.UNKNOWN_USER);
@ -523,8 +531,8 @@ public class AuthenticationProcessor {
}
UserModel authUser = clientSession.getAuthenticatedUser();
validateUser(authUser);
FlowExecution flowExecution = createFlowExecution(this.flowId);
Response challenge = flowExecution.processFlow();
AuthenticationFlow authenticationFlow = createFlowExecution(this.flowId, null);
Response challenge = authenticationFlow.processFlow();
if (challenge != null) return challenge;
String username = clientSession.getAuthenticatedUser().getUsername();
@ -572,175 +580,16 @@ public class AuthenticationProcessor {
}
TokenManager.attachClientSession(userSession, clientSession);
event.user(userSession.getUser())
.detail(Details.USERNAME, username)
.session(userSession);
.detail(Details.USERNAME, username)
.session(userSession);
return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, event);
}
class FlowExecution {
Response alternativeChallenge = null;
AuthenticationExecutionModel challengedAlternativeExecution = null;
boolean alternativeSuccessful = false;
Iterator<AuthenticationExecutionModel> executions;
public Response action(String actionExecution, Result actionResult) {
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, actionResult);
} else {
throw new AuthException("action is not current execution", Error.INTERNAL_ERROR);
}
} else { // we found the action
Response response = processResult(actionResult);
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(Result 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.challenge != null) {
return sendChallenge(result, execution);
}
throw new AuthException(result.error);
} 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.challenge;
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(Result result, AuthenticationExecutionModel execution) {
clientSession.setNote(CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
return result.challenge;
}
}
public AuthenticatorContext createAuthenticatorContext(AuthenticationExecutionModel model, Authenticator authenticator) {
return new Result(model, authenticator);
}
}

View file

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

View file

@ -77,4 +77,8 @@ public interface AuthenticatorContext {
* @return
*/
String generateAccessCode();
Response getChallenge();
AuthenticationProcessor.Error getError();
}

View file

@ -24,7 +24,7 @@ public class AuthenticatorUtil {
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
executions.add(model);
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) {
for (AuthenticationExecutionModel model : realm.getAuthenticationExecutions(flowId)) {
if (model.isAutheticatorFlow()) {
AuthenticationExecutionModel recurse = findExecutionByAuthenticator(realm, model.getAuthenticator(), authProviderId);
AuthenticationExecutionModel recurse = findExecutionByAuthenticator(realm, model.getFlowId(), authProviderId);
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.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.keycloak.Config;
@ -6,13 +6,11 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.util.Time;
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.keycloak.Config;
@ -6,13 +6,9 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.ClientSessionCode;
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.keycloak.Config;
@ -6,19 +6,13 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.util.Time;
import javax.ws.rs.core.Response;
import java.util.concurrent.TimeUnit;
/**
* @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.keycloak.Config;
@ -8,13 +8,11 @@ import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.events.Details;
import org.keycloak.events.EventType;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.Time;

View file

@ -300,7 +300,6 @@ public class LoginActionsService {
*/
@Path("authenticate")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response authenticateForm(@QueryParam("code") String code,
@QueryParam("execution") String execution) {
event.event(EventType.LOGIN);

View file

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

View file

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

View file

@ -4,4 +4,6 @@ org.keycloak.exportimport.ClientImportSpi
org.keycloak.wellknown.WellKnownSpi
org.keycloak.messages.MessagesSpi
org.keycloak.authentication.AuthenticatorSpi
org.keycloak.authentication.RequiredActionSpi
org.keycloak.authentication.RequiredActionSpi
org.keycloak.authentication.FormAuthenticatorSpi
org.keycloak.authentication.FormActionSpi