From 064d677fdce9d44967ee2ac7fcddef7857afe3b9 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 24 Jun 2015 21:07:38 -0400 Subject: [PATCH] form auth --- .../META-INF/jpa-changelog-1.4.0.xml | 10 + .../models/AuthenticationExecutionModel.java | 14 + .../models/AuthenticationFlowModel.java | 9 + .../AuthenticationExecutionEntity.java | 9 + .../entities/AuthenticationFlowEntity.java | 10 + .../utils/DefaultAuthenticationFlows.java | 4 +- .../models/file/adapter/RealmAdapter.java | 6 + .../org/keycloak/models/jpa/RealmAdapter.java | 12 +- .../AuthenticationExecutionEntity.java | 25 +- .../entities/AuthenticationFlowEntity.java | 13 +- .../mongo/keycloak/adapters/RealmAdapter.java | 5 + .../authentication/AuthenticationFlow.java | 12 + .../AuthenticationProcessor.java | 226 ++------------ .../authentication/Authenticator.java | 2 +- .../authentication/AuthenticatorUtil.java | 4 +- .../DefaultAuthenticationFlow.java | 207 +++++++++++++ .../keycloak/authentication/FormAction.java | 24 ++ .../authentication/FormActionFactory.java | 10 + .../authentication/FormActionSpi.java | 32 ++ .../FormAuthenticationFlow.java | 290 ++++++++++++++++++ .../authentication/FormAuthenticator.java | 14 + .../FormAuthenticatorFactory.java | 10 + .../authentication/FormAuthenticatorSpi.java | 32 ++ .../keycloak/authentication/FormContext.java | 9 + .../TermsAndConditions.java | 2 +- .../UpdatePassword.java | 4 +- .../UpdateProfile.java | 6 +- .../UpdateTotp.java | 8 +- .../VerifyEmail.java | 4 +- .../AuthenticationManagementResource.java | 2 +- ...cloak.authentication.RequiredActionFactory | 10 +- .../services/org.keycloak.provider.Spi | 4 +- 32 files changed, 794 insertions(+), 235 deletions(-) create mode 100755 services/src/main/java/org/keycloak/authentication/AuthenticationFlow.java create mode 100755 services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java create mode 100755 services/src/main/java/org/keycloak/authentication/FormAction.java create mode 100755 services/src/main/java/org/keycloak/authentication/FormActionFactory.java create mode 100755 services/src/main/java/org/keycloak/authentication/FormActionSpi.java create mode 100755 services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java create mode 100755 services/src/main/java/org/keycloak/authentication/FormAuthenticator.java create mode 100755 services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java create mode 100755 services/src/main/java/org/keycloak/authentication/FormAuthenticatorSpi.java create mode 100755 services/src/main/java/org/keycloak/authentication/FormContext.java rename services/src/main/java/org/keycloak/authentication/{actions => requiredactions}/TermsAndConditions.java (95%) rename services/src/main/java/org/keycloak/authentication/{actions => requiredactions}/UpdatePassword.java (92%) rename services/src/main/java/org/keycloak/authentication/{actions => requiredactions}/UpdateProfile.java (86%) rename services/src/main/java/org/keycloak/authentication/{actions => requiredactions}/UpdateTotp.java (86%) rename services/src/main/java/org/keycloak/authentication/{actions => requiredactions}/VerifyEmail.java (93%) diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml index 3134b12a33b..ff0311dfa97 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.4.0.xml @@ -12,6 +12,16 @@ + + + + + + + + + + diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java index 15a599f6e2b..3b959d95fc1 100755 --- a/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java +++ b/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java @@ -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? * diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationFlowModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationFlowModel.java index 194a6b39d0b..db5970ada24 100755 --- a/model/api/src/main/java/org/keycloak/models/AuthenticationFlowModel.java +++ b/model/api/src/main/java/org/keycloak/models/AuthenticationFlowModel.java @@ -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; + } } diff --git a/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java index e98b37188b1..123e6258a52 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java @@ -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; + } } diff --git a/model/api/src/main/java/org/keycloak/models/entities/AuthenticationFlowEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationFlowEntity.java index b79d1a5f9d9..0db9560ea17 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/AuthenticationFlowEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationFlowEntity.java @@ -12,6 +12,8 @@ public class AuthenticationFlowEntity { protected String id; protected String alias; protected String description; + protected String providerId; + List executions = new ArrayList(); public String getId() { return id; @@ -44,4 +46,12 @@ public class AuthenticationFlowEntity { public void setExecutions(List executions) { this.executions = executions; } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index df2e677dccb..751242295b4 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -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); diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java index 1dc6fabe931..7df3f6ff13e 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java @@ -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()); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index de474998961..eec43ba82a8 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -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 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 queryResult = query.getResultList(); List 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(); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java index be8720aad86..b27659f445c 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java @@ -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; } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java index 1a60832cc1f..2db9722973b 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java @@ -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 executions = new ArrayList(); public String getId() { return id; @@ -80,4 +83,12 @@ public class AuthenticationFlowEntity { public void setExecutions(Collection executions) { this.executions = executions; } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index dee96e217b5..935746efe53 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1341,6 +1341,7 @@ public class RealmAdapter extends AbstractMongoAdapter 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 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 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 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 impleme entity.setAuthenticator(model.getAuthenticator()); entity.setPriority(model.getPriority()); entity.setRequirement(model.getRequirement()); + entity.setFlowId(model.getFlowId()); entity.setUserSetupAllowed(model.isUserSetupAllowed()); updateMongoEntity(); } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/AuthenticationFlow.java new file mode 100755 index 00000000000..d77772e7197 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationFlow.java @@ -0,0 +1,12 @@ +package org.keycloak.authentication; + +import javax.ws.rs.core.Response; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface AuthenticationFlow { + Response processAction(String actionExecution); + Response processFlow(); +} diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index 904e47b8606..f5ff56e9b06 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -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 Bill Burke @@ -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); } @@ -390,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); @@ -406,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); } @@ -423,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 { @@ -447,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); @@ -456,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(); @@ -488,8 +499,8 @@ public class AuthenticationProcessor { event.detail(Details.AUTH_TYPE, authType); } - FlowExecution flowExecution = createFlowExecution(this.flowId); - Response challenge = flowExecution.action(execution); + 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); @@ -520,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(); @@ -569,189 +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 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(); - } - - - } - - - + public AuthenticatorContext createAuthenticatorContext(AuthenticationExecutionModel model, Authenticator authenticator) { + return new Result(model, authenticator); + } } diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java index 36e6d52b7ff..ddde806e6d6 100755 --- a/services/src/main/java/org/keycloak/authentication/Authenticator.java +++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java @@ -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); /** diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java index 591ca4b7d95..04962dd65f0 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorUtil.java @@ -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; } diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java new file mode 100755 index 00000000000..8aaaa643e3c --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java @@ -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 Bill Burke +* @version $Revision: 1 $ +*/ +public class DefaultAuthenticationFlow implements AuthenticationFlow { + Response alternativeChallenge = null; + AuthenticationExecutionModel challengedAlternativeExecution = null; + boolean alternativeSuccessful = false; + Iterator 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(); + } + + +} diff --git a/services/src/main/java/org/keycloak/authentication/FormAction.java b/services/src/main/java/org/keycloak/authentication/FormAction.java new file mode 100755 index 00000000000..90bd0841877 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormAction.java @@ -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 Bill Burke + * @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); + +} diff --git a/services/src/main/java/org/keycloak/authentication/FormActionFactory.java b/services/src/main/java/org/keycloak/authentication/FormActionFactory.java new file mode 100755 index 00000000000..cbac58b611e --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormActionFactory.java @@ -0,0 +1,10 @@ +package org.keycloak.authentication; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface FormActionFactory extends ProviderFactory { +} diff --git a/services/src/main/java/org/keycloak/authentication/FormActionSpi.java b/services/src/main/java/org/keycloak/authentication/FormActionSpi.java new file mode 100755 index 00000000000..125ffdcfe19 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormActionSpi.java @@ -0,0 +1,32 @@ +package org.keycloak.authentication; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Stian Thorgersen + */ +public class FormActionSpi implements Spi { + + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "form-action"; + } + + @Override + public Class getProviderClass() { + return FormAction.class; + } + + @Override + public Class getProviderFactoryClass() { + return FormActionFactory.class; + } + +} diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java new file mode 100755 index 00000000000..b8bb6913485 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java @@ -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 Bill Burke +* @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(); + } + + +} diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java new file mode 100755 index 00000000000..4804e926355 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticator.java @@ -0,0 +1,14 @@ +package org.keycloak.authentication; + +import org.keycloak.provider.Provider; + +import javax.ws.rs.core.Response; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface FormAuthenticator extends Provider { + void authenticate(AuthenticatorContext context); + Response createChallenge(FormContext context, String... errorMessages); +} diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java new file mode 100755 index 00000000000..5b99f1a764e --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticatorFactory.java @@ -0,0 +1,10 @@ +package org.keycloak.authentication; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface FormAuthenticatorFactory extends ProviderFactory { +} diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticatorSpi.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticatorSpi.java new file mode 100755 index 00000000000..18303f0bbb7 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticatorSpi.java @@ -0,0 +1,32 @@ +package org.keycloak.authentication; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Stian Thorgersen + */ +public class FormAuthenticatorSpi implements Spi { + + @Override + public boolean isInternal() { + return false; + } + + @Override + public String getName() { + return "form-authenticator"; + } + + @Override + public Class getProviderClass() { + return FormAuthenticator.class; + } + + @Override + public Class getProviderFactoryClass() { + return FormAuthenticatorFactory.class; + } + +} diff --git a/services/src/main/java/org/keycloak/authentication/FormContext.java b/services/src/main/java/org/keycloak/authentication/FormContext.java new file mode 100755 index 00000000000..24c46a71a76 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/FormContext.java @@ -0,0 +1,9 @@ +package org.keycloak.authentication; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface FormContext extends AuthenticatorContext { + FormAuthenticator getFormAuthenticator(); +} diff --git a/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java b/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java similarity index 95% rename from services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java rename to services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java index 5c6157ca240..da6d0613512 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/TermsAndConditions.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/TermsAndConditions.java @@ -1,4 +1,4 @@ -package org.keycloak.authentication.actions; +package org.keycloak.authentication.requiredactions; import org.keycloak.Config; import org.keycloak.authentication.RequiredActionContext; diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java similarity index 92% rename from services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java rename to services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java index ae4a3808fdd..ab6eb27bb00 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/UpdatePassword.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdatePassword.java @@ -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; diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java similarity index 86% rename from services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java rename to services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java index 7117ae320db..e353cce1d88 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/UpdateProfile.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateProfile.java @@ -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; diff --git a/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java similarity index 86% rename from services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java rename to services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java index 6f79d518cf4..dd41927978a 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/UpdateTotp.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/UpdateTotp.java @@ -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 Bill Burke diff --git a/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java similarity index 93% rename from services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java rename to services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java index fa284bd25c2..33b43330341 100755 --- a/services/src/main/java/org/keycloak/authentication/actions/VerifyEmail.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java @@ -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; diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index d048a571a0f..7f1edbd61e5 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -124,7 +124,7 @@ public class AuthenticationManagementResource { rep.setSubFlow(false); rep.setRequirementChoices(new LinkedList()); 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()); diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory index 8106ec7fce9..a72fdd9fdd0 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactory @@ -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 \ No newline at end of file +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 \ No newline at end of file diff --git a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 050fef24d62..43d8d10524d 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/services/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -4,4 +4,6 @@ org.keycloak.exportimport.ClientImportSpi org.keycloak.wellknown.WellKnownSpi org.keycloak.messages.MessagesSpi org.keycloak.authentication.AuthenticatorSpi -org.keycloak.authentication.RequiredActionSpi \ No newline at end of file +org.keycloak.authentication.RequiredActionSpi +org.keycloak.authentication.FormAuthenticatorSpi +org.keycloak.authentication.FormActionSpi