From 1bce639d352f3bb7da9d3fbfb3007398fa3fa1e1 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 18 Jun 2015 16:48:28 -0400 Subject: [PATCH 1/2] refactor flow first phase --- .../AuthenticationProcessor.java | 295 +++++++++++------- .../authentication/Authenticator2.java | 18 ++ 2 files changed, 192 insertions(+), 121 deletions(-) create mode 100755 services/src/main/java/org/keycloak/authentication/Authenticator2.java diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index fcc3a76892..bbafc76231 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -17,6 +17,7 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.services.ErrorPage; +import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.BruteForceProtector; import org.keycloak.services.managers.ClientSessionCode; @@ -25,6 +26,7 @@ import org.keycloak.services.messages.Messages; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import java.util.Collections; +import java.util.Iterator; import java.util.List; /** @@ -414,6 +416,16 @@ public class AuthenticationProcessor { } + public FlowExecution createFlowExecution(String flowId) { + 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; + } public Response authenticate() throws AuthException { checkClientSession(); @@ -428,7 +440,8 @@ public class AuthenticationProcessor { } UserModel authUser = clientSession.getAuthenticatedUser(); validateUser(authUser); - Response challenge = processFlow(flowId); + FlowExecution flowExecution = createFlowExecution(this.flowId); + Response challenge = flowExecution.processFlow(); if (challenge != null) return challenge; if (clientSession.getAuthenticatedUser() == null) { throw new AuthException(Error.UNKNOWN_USER); @@ -458,7 +471,8 @@ public class AuthenticationProcessor { } UserModel authUser = clientSession.getAuthenticatedUser(); validateUser(authUser); - Response challenge = processFlow(flowId); + FlowExecution flowExecution = createFlowExecution(this.flowId); + Response challenge = flowExecution.processFlow(); if (challenge != null) return challenge; String username = clientSession.getAuthenticatedUser().getUsername(); @@ -482,125 +496,6 @@ public class AuthenticationProcessor { } - public Response processFlow(String flowId) { - AuthenticationFlowModel flow = realm.getAuthenticationFlowById(flowId); - if (flow == null) { - logger.error("Unknown flow to execute with"); - throw new AuthException(Error.INTERNAL_ERROR); - } - List executions = realm.getAuthenticationExecutions(flowId); - if (executions == null) return null; - Response alternativeChallenge = null; - AuthenticationExecutionModel challengedAlternativeExecution = null; - boolean alternativeSuccessful = false; - for (AuthenticationExecutionModel model : executions) { - if (isProcessed(model)) { - logger.debug("execution is processed"); - if (!alternativeSuccessful && model.isAlternative() && isSuccessful(model)) alternativeSuccessful = true; - continue; - } - Result context = null; - if (model.isAlternative() && alternativeSuccessful) { - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); - continue; - } - if (model.isAutheticatorFlow()) { - Response flowResponse = processFlow(model.getAuthenticator()); - if (flowResponse == null) { - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS); - if (model.isAlternative()) alternativeSuccessful = true; - continue; - } else { - return flowResponse; - } - - } - - AuthenticatorModel authenticatorModel = realm.getAuthenticatorById(model.getAuthenticator()); - AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticatorModel.getProviderId()); - Authenticator authenticator = factory.create(authenticatorModel); - logger.debugv("authenticator: {0}", authenticatorModel.getProviderId()); - UserModel authUser = clientSession.getAuthenticatedUser(); - - if (authenticator.requiresUser() && authUser == null){ - if (alternativeChallenge != null) { - clientSession.setAuthenticatorStatus(challengedAlternativeExecution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); - return alternativeChallenge; - } - throw new AuthException("authenticator: " + authenticatorModel.getProviderId(), 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}", authenticatorModel.getProviderId()); - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED); - authenticator.setRequiredActions(session, realm, clientSession.getAuthenticatedUser()); - continue; - } else { - throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED); - } - } else if (model.isOptional()) { - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); - continue; - } - } - } - context = new Result(model, authenticatorModel, authenticator); - authenticator.authenticate(context); - Status result = context.getStatus(); - if (result == Status.SUCCESS){ - logger.debugv("authenticator SUCCESS: {0}", authenticatorModel.getProviderId()); - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS); - if (model.isAlternative()) alternativeSuccessful = true; - continue; - } else if (result == Status.FAILED) { - logger.debugv("authenticator FAILED: {0}", authenticatorModel.getProviderId()); - logUserFailure(); - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.FAILED); - if (context.challenge != null) return context.challenge; - throw new AuthException(context.error); - } else if (result == Status.FORCE_CHALLENGE) { - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); - return context.challenge; - } else if (result == Status.CHALLENGE) { - logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId()); - if (model.isRequired() || (model.isOptional() && configuredFor)) { - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); - return context.challenge; - } - else if (model.isAlternative()) { - alternativeChallenge = context.challenge; - challengedAlternativeExecution = model; - } else { - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); - } - continue; - } else if (result == Status.FAILURE_CHALLENGE) { - logger.debugv("authenticator FAILURE_CHALLENGE: {0}", authenticatorModel.getProviderId()); - logUserFailure(); - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); - return context.challenge; - } else if (result == Status.ATTEMPTED) { - logger.debugv("authenticator ATTEMPTED: {0}", authenticatorModel.getProviderId()); - if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { - throw new AuthException(Error.INVALID_CREDENTIALS); - } - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.ATTEMPTED); - continue; - } else { - logger.debugv("authenticator INTERNAL_ERROR: {0}", authenticatorModel.getProviderId()); - logger.error("Unknown result status"); - throw new AuthException(Error.INTERNAL_ERROR); - } - } - return null; - } - - - public void validateUser(UserModel authenticatedUser) { if (authenticatedUser != null) { if (!authenticatedUser.isEnabled()) throw new AuthException(Error.USER_DISABLED); @@ -632,5 +527,163 @@ public class AuthenticationProcessor { } + class FlowExecution { + Response alternativeChallenge = null; + AuthenticationExecutionModel challengedAlternativeExecution = null; + boolean alternativeSuccessful = false; + Iterator 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.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); + continue; + } + if (model.isAutheticatorFlow()) { + FlowExecution flowExecution = createFlowExecution(model.getAuthenticator()); + Response flowResponse = flowExecution.processFlow(); + if (flowResponse == null) { + clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS); + if (model.isAlternative()) alternativeSuccessful = true; + continue; + } else { + return flowResponse; + } + + } + + AuthenticatorModel authenticatorModel = realm.getAuthenticatorById(model.getAuthenticator()); + AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticatorModel.getProviderId()); + Authenticator authenticator = factory.create(authenticatorModel); + logger.debugv("authenticator: {0}", authenticatorModel.getProviderId()); + UserModel authUser = clientSession.getAuthenticatedUser(); + + if (authenticator.requiresUser() && authUser == null){ + if (alternativeChallenge != null) { + clientSession.setAuthenticatorStatus(challengedAlternativeExecution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); + return alternativeChallenge; + } + throw new AuthException("authenticator: " + authenticatorModel.getProviderId(), 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}", authenticatorModel.getProviderId()); + clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED); + authenticator.setRequiredActions(session, realm, clientSession.getAuthenticatedUser()); + continue; + } else { + throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED); + } + } else if (model.isOptional()) { + clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); + continue; + } + } + } + Result context = new Result(model, authenticatorModel, authenticator); + authenticator.authenticate(context); + Response response = processResult(context); + if (response != null) return response; + } + return null; + } + + + public Response processResult(Result result) { + AuthenticationExecutionModel execution = result.getExecution(); + AuthenticatorModel authenticatorModel = result.getAuthenticatorModel(); + Status status = result.getStatus(); + if (status == Status.SUCCESS){ + logger.debugv("authenticator SUCCESS: {0}", authenticatorModel.getProviderId()); + clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS); + if (execution.isAlternative()) alternativeSuccessful = true; + return null; + } else if (status == Status.FAILED) { + logger.debugv("authenticator FAILED: {0}", authenticatorModel.getProviderId()); + logUserFailure(); + clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.FAILED); + if (result.challenge != null) return result.challenge; + throw new AuthException(result.error); + } else if (status == Status.FORCE_CHALLENGE) { + clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); + return result.challenge; + } else if (status == Status.CHALLENGE) { + logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId()); + if (execution.isRequired()) { + clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); + return result.challenge; + } + UserModel authenticatedUser = clientSession.getAuthenticatedUser(); + if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(session, realm, authenticatedUser)) { + clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); + return result.challenge; + } + if (execution.isAlternative()) { + alternativeChallenge = result.challenge; + challengedAlternativeExecution = execution; + } else { + clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); + } + return null; + } else if (status == Status.FAILURE_CHALLENGE) { + logger.debugv("authenticator FAILURE_CHALLENGE: {0}", authenticatorModel.getProviderId()); + logUserFailure(); + clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); + return result.challenge; + } else if (status == Status.ATTEMPTED) { + logger.debugv("authenticator ATTEMPTED: {0}", authenticatorModel.getProviderId()); + if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { + throw new AuthException(Error.INVALID_CREDENTIALS); + } + clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.ATTEMPTED); + return null; + } else { + logger.debugv("authenticator INTERNAL_ERROR: {0}", authenticatorModel.getProviderId()); + logger.error("Unknown result status"); + throw new AuthException(Error.INTERNAL_ERROR); + } + + } + + + } + + + + } diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator2.java b/services/src/main/java/org/keycloak/authentication/Authenticator2.java new file mode 100755 index 0000000000..f78d2839e9 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/Authenticator2.java @@ -0,0 +1,18 @@ +package org.keycloak.authentication; + +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.Provider; +import sun.security.krb5.internal.AuthContext; + +/** +* @author Bill Burke +* @version $Revision: 1 $ +*/ +public interface Authenticator2 extends Authenticator { + void putAction(AuthContext context); + void postAction(AuthContext context); + void deleteAction(AuthContext context); + void getAction(AuthContext context); +} From 84faac0cd61353b4d6c37a3ac092e85070b4dcaf Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 18 Jun 2015 20:01:12 -0400 Subject: [PATCH 2/2] handle page refresh better --- .../keycloak/models/ClientSessionModel.java | 16 ++- .../org/keycloak/models/UserSessionModel.java | 9 -- .../infinispan/ClientSessionAdapter.java | 18 ++- .../entities/ClientSessionEntity.java | 7 +- .../sessions/jpa/ClientSessionAdapter.java | 43 +++++- .../ClientSessionAuthStatusEntity.java | 8 +- .../sessions/mem/ClientSessionAdapter.java | 13 +- .../mem/entities/ClientSessionEntity.java | 7 +- .../sessions/mongo/ClientSessionAdapter.java | 12 +- .../entities/MongoClientSessionEntity.java | 7 +- .../AuthenticationProcessor.java | 127 +++++++++++++----- .../authentication/Authenticator.java | 2 + .../authentication/Authenticator2.java | 18 --- .../AbstractFormAuthenticator.java | 28 ++-- .../authenticators/CookieAuthenticator.java | 5 + .../authenticators/OTPFormAuthenticator.java | 17 ++- .../authenticators/SpnegoAuthenticator.java | 31 +---- .../authenticators/UsernamePasswordForm.java | 58 ++++---- .../services/managers/ClientSessionCode.java | 19 --- .../resources/LoginActionsService.java | 91 ++++++------- 20 files changed, 298 insertions(+), 238 deletions(-) delete mode 100755 services/src/main/java/org/keycloak/authentication/Authenticator2.java diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java index 01f524a4c4..cd724efe0d 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java @@ -32,9 +32,9 @@ public interface ClientSessionModel { public Set getProtocolMappers(); public void setProtocolMappers(Set protocolMappers); - public Map getAuthenticators(); - public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status); - public void setAuthenticatorStatus(Map status); + public Map getExecutionStatus(); + public void setExecutionStatus(String authenticator, ExecutionStatus status); + public void clearExecutionStatus(); public UserModel getAuthenticatedUser(); public void setAuthenticatedUser(UserModel user); @@ -67,6 +67,8 @@ public interface ClientSessionModel { */ public Map getUserSessionNotes(); + public void clearUserSessionNotes(); + public static enum Action { OAUTH_GRANT, CODE_TO_TOKEN, @@ -80,4 +82,12 @@ public interface ClientSessionModel { LOGGED_OUT } + public enum ExecutionStatus { + FAILED, + SUCCESS, + SETUP_REQUIRED, + ATTEMPTED, + SKIPPED, + CHALLENGED + } } diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java index 6af29cf295..516ba26c32 100755 --- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java +++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java @@ -37,15 +37,6 @@ public interface UserSessionModel { List getClientSessions(); - public static enum AuthenticatorStatus { - FAILED, - SUCCESS, - SETUP_REQUIRED, - ATTEMPTED, - SKIPPED, - CHALLENGED - } - public String getNote(String name); public void setNote(String name, String value); public void removeNote(String name); diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java index 78be9ee8e5..a00b53941b 100755 --- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java +++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java @@ -185,23 +185,32 @@ public class ClientSessionAdapter implements ClientSessionModel { return copy; } + @Override + public void clearUserSessionNotes() { + entity.setUserSessionNotes(new HashMap()); + update(); + + } + void update() { provider.getTx().replace(cache, entity.getId(), entity); } @Override - public Map getAuthenticators() { + public Map getExecutionStatus() { return entity.getAuthenticatorStatus(); } @Override - public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { + public void setExecutionStatus(String authenticator, ExecutionStatus status) { entity.getAuthenticatorStatus().put(authenticator, status); + update(); } @Override - public void setAuthenticatorStatus(Map status) { - entity.setAuthenticatorStatus(status); + public void clearExecutionStatus() { + entity.getAuthenticatorStatus().clear(); + update(); } @Override @@ -211,6 +220,7 @@ public class ClientSessionAdapter implements ClientSessionModel { @Override public void setAuthenticatedUser(UserModel user) { entity.setAuthUserId(user.getId()); + update(); } diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java index 340bf92dd9..3cb6614248 100755 --- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java +++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java @@ -1,7 +1,6 @@ package org.keycloak.models.sessions.infinispan.entities; import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.UserSessionModel; import java.util.HashMap; import java.util.Map; @@ -30,7 +29,7 @@ public class ClientSessionEntity extends SessionEntity { private Set protocolMappers; private Map notes; private Map userSessionNotes; - private Map authenticatorStatus = new HashMap<>(); + private Map authenticatorStatus = new HashMap<>(); private String authUserId; public String getClient() { @@ -113,11 +112,11 @@ public class ClientSessionEntity extends SessionEntity { this.notes = notes; } - public Map getAuthenticatorStatus() { + public Map getAuthenticatorStatus() { return authenticatorStatus; } - public void setAuthenticatorStatus(Map authenticatorStatus) { + public void setAuthenticatorStatus(Map authenticatorStatus) { this.authenticatorStatus = authenticatorStatus; } diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java index 2ce7034363..e8498e9dde 100755 --- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java @@ -6,6 +6,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity; import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity; import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity; import org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity; @@ -106,6 +107,17 @@ public class ClientSessionAdapter implements ClientSessionModel { return copy; } + @Override + public void clearUserSessionNotes() { + Iterator it = entity.getUserSessionNotes().iterator(); + while (it.hasNext()) { + ClientUserSessionNoteEntity attr = it.next(); + it.remove(); + em.remove(attr); + } + + } + @Override public String getId() { return entity.getId(); @@ -242,27 +254,44 @@ public class ClientSessionAdapter implements ClientSessionModel { } @Override - public Map getAuthenticators() { - return null; + public Map getExecutionStatus() { + Map result = new HashMap<>(); + for (ClientSessionAuthStatusEntity status : entity.getAuthanticatorStatus()) { + result.put(status.getAuthenticator(), status.getStatus()); + } + return result; } @Override - public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { + public void setExecutionStatus(String authenticator, ExecutionStatus status) { + ClientSessionAuthStatusEntity authStatus = new ClientSessionAuthStatusEntity(); + authStatus.setAuthenticator(authenticator); + authStatus.setClientSession(entity); + authStatus.setStatus(status); + em.persist(authStatus); + entity.getAuthanticatorStatus().add(authStatus); + em.flush(); + } @Override - public void setAuthenticatorStatus(Map status) { - + public void clearExecutionStatus() { + Iterator iterator = entity.getAuthanticatorStatus().iterator(); + while (iterator.hasNext()) { + ClientSessionAuthStatusEntity authStatus = iterator.next(); + iterator.remove(); + em.remove(authStatus); + } } @Override public UserModel getAuthenticatedUser() { - return null; + return session.users().getUserById(entity.getUserId(), realm); } @Override public void setAuthenticatedUser(UserModel user) { - + entity.setUserId(user.getId()); } } diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java index 49afbab2f7..e8dea7a0c9 100755 --- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java @@ -1,6 +1,6 @@ package org.keycloak.models.sessions.jpa.entities; -import org.keycloak.models.UserSessionModel; +import org.keycloak.models.ClientSessionModel; import javax.persistence.Column; import javax.persistence.Entity; @@ -39,7 +39,7 @@ public class ClientSessionAuthStatusEntity { @Column(name = "AUTHENTICATOR") protected String authenticator; @Column(name = "STATUS") - protected UserSessionModel.AuthenticatorStatus status; + protected ClientSessionModel.ExecutionStatus status; public String getAuthenticator() { return authenticator; @@ -49,11 +49,11 @@ public class ClientSessionAuthStatusEntity { this.authenticator = authenticator; } - public UserSessionModel.AuthenticatorStatus getStatus() { + public ClientSessionModel.ExecutionStatus getStatus() { return status; } - public void setStatus(UserSessionModel.AuthenticatorStatus status) { + public void setStatus(ClientSessionModel.ExecutionStatus status) { this.status = status; } diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java index 0e0647fd8e..f9e52d095b 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java @@ -155,19 +155,24 @@ public class ClientSessionAdapter implements ClientSessionModel { } @Override - public Map getAuthenticators() { + public Map getExecutionStatus() { return entity.getAuthenticatorStatus(); } @Override - public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { + public void setExecutionStatus(String authenticator, ExecutionStatus status) { entity.getAuthenticatorStatus().put(authenticator, status); } @Override - public void setAuthenticatorStatus(Map status) { - entity.setAuthenticatorStatus(status); + public void clearExecutionStatus() { + entity.getAuthenticatorStatus().clear(); + } + + @Override + public void clearUserSessionNotes() { + entity.getUserSessionNotes().clear(); } @Override diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java index e76f62499d..da5e050772 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java @@ -1,7 +1,6 @@ package org.keycloak.models.sessions.mem.entities; import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.UserSessionModel; import java.util.HashMap; import java.util.Map; @@ -15,7 +14,7 @@ public class ClientSessionEntity { private String id; private String clientId; private String realmId; - private Map authenticatorStatus = new HashMap<>(); + private Map authenticatorStatus = new HashMap<>(); private String authUserId; private UserSessionEntity session; @@ -122,11 +121,11 @@ public class ClientSessionEntity { this.authUserId = authUserId; } - public Map getAuthenticatorStatus() { + public Map getAuthenticatorStatus() { return authenticatorStatus; } - public void setAuthenticatorStatus(Map authenticatorStatus) { + public void setAuthenticatorStatus(Map authenticatorStatus) { this.authenticatorStatus = authenticatorStatus; } diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java index ad1d0d7ee2..4ad1d5194a 100755 --- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java +++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java @@ -171,22 +171,26 @@ public class ClientSessionAdapter extends AbstractMongoAdapter getAuthenticators() { + public Map getExecutionStatus() { return entity.getAuthenticatorStatus(); } @Override - public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { + public void setExecutionStatus(String authenticator, ExecutionStatus status) { entity.getAuthenticatorStatus().put(authenticator, status); updateMongoEntity(); } @Override - public void setAuthenticatorStatus(Map status) { - entity.setAuthenticatorStatus(status); + public void clearExecutionStatus() { + entity.getAuthenticatorStatus().clear(); updateMongoEntity(); + } + @Override + public void clearUserSessionNotes() { + entity.getUserSessionNotes().clear(); } @Override diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java index 21831b6ef8..de7bed3cd5 100755 --- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java +++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java @@ -4,7 +4,6 @@ import org.keycloak.connections.mongo.api.MongoCollection; import org.keycloak.connections.mongo.api.MongoIdentifiableEntity; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.ClientSessionModel; -import org.keycloak.models.UserSessionModel; import org.keycloak.models.entities.AbstractIdentifiableEntity; import java.util.HashMap; @@ -31,7 +30,7 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme private List protocolMappers; private Map notes = new HashMap(); private Map userSessionNotes = new HashMap(); - private Map authenticatorStatus = new HashMap<>(); + private Map authenticatorStatus = new HashMap<>(); private String authUserId; public String getId() { @@ -130,11 +129,11 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme this.sessionId = sessionId; } - public Map getAuthenticatorStatus() { + public Map getAuthenticatorStatus() { return authenticatorStatus; } - public void setAuthenticatorStatus(Map authenticatorStatus) { + public void setAuthenticatorStatus(Map authenticatorStatus) { this.authenticatorStatus = authenticatorStatus; } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index bbafc76231..fce95a26a7 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -3,6 +3,7 @@ package org.keycloak.authentication; import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.ClientConnection; +import org.keycloak.authentication.authenticators.AbstractFormAuthenticator; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; @@ -17,23 +18,23 @@ import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.services.ErrorPage; -import org.keycloak.services.managers.Auth; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.BruteForceProtector; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; +import org.keycloak.util.Time; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; -import java.util.List; /** * @author Bill Burke * @version $Revision: 1 $ */ public class AuthenticationProcessor { + public static final String CURRENT_AUTHENTICATION_EXECUTION = "current.authentication.execution"; protected static Logger logger = Logger.getLogger(AuthenticationProcessor.class); protected RealmModel realm; protected UserSessionModel userSession; @@ -325,7 +326,7 @@ public class AuthenticationProcessor { @Override public String generateAccessCode() { ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession()); - accessCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); + clientSession.setTimestamp(Time.currentTime()); return accessCode.getCode(); } } @@ -362,23 +363,32 @@ public class AuthenticationProcessor { } } - public void logUserFailure() { + public void logFailure() { + if (realm.isBruteForceProtected()) { + String username = clientSession.getNote(AbstractFormAuthenticator.ATTEMPTED_USERNAME); + // todo need to handle non form failures + if (username == null) { + } else { + protector.failedLogin(realm, username, connection); + + } + } } protected boolean isProcessed(AuthenticationExecutionModel model) { if (model.isDisabled()) return true; - UserSessionModel.AuthenticatorStatus status = clientSession.getAuthenticators().get(model.getId()); + ClientSessionModel.ExecutionStatus status = clientSession.getExecutionStatus().get(model.getId()); if (status == null) return false; - return status == UserSessionModel.AuthenticatorStatus.SUCCESS || status == UserSessionModel.AuthenticatorStatus.SKIPPED - || status == UserSessionModel.AuthenticatorStatus.ATTEMPTED - || status == UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED; + return status == ClientSessionModel.ExecutionStatus.SUCCESS || status == ClientSessionModel.ExecutionStatus.SKIPPED + || status == ClientSessionModel.ExecutionStatus.ATTEMPTED + || status == ClientSessionModel.ExecutionStatus.SETUP_REQUIRED; } public boolean isSuccessful(AuthenticationExecutionModel model) { - UserSessionModel.AuthenticatorStatus status = clientSession.getAuthenticators().get(model.getId()); + ClientSessionModel.ExecutionStatus status = clientSession.getExecutionStatus().get(model.getId()); if (status == null) return false; - return status == UserSessionModel.AuthenticatorStatus.SUCCESS; + return status == ClientSessionModel.ExecutionStatus.SUCCESS; } public Response handleBrowserException(Exception failure) { @@ -449,6 +459,51 @@ public class AuthenticationProcessor { return authenticationComplete(); } + protected void resetFlow() { + clientSession.clearExecutionStatus(); + clientSession.clearUserSessionNotes(); + clientSession.removeNote(CURRENT_AUTHENTICATION_EXECUTION); + } + + public Response authenticationAction(String execution) { + checkClientSession(); + String current = clientSession.getNote(CURRENT_AUTHENTICATION_EXECUTION); + if (!execution.equals(current)) { + logger.debug("Current execution does not equal executed execution. Might be a page refresh"); + logFailure(); + resetFlow(); + return authenticate(); + } + AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); + if (model == null) { + logger.debug("Cannot find execution, reseting flow"); + logFailure(); + resetFlow(); + return authenticate(); + } + event.event(EventType.LOGIN); + event.client(clientSession.getClient().getClientId()) + .detail(Details.REDIRECT_URI, clientSession.getRedirectUri()) + .detail(Details.AUTH_METHOD, clientSession.getAuthMethod()); + String authType = clientSession.getNote(Details.AUTH_TYPE); + if (authType != null) { + event.detail(Details.AUTH_TYPE, authType); + } + AuthenticatorModel authenticatorModel = realm.getAuthenticatorById(model.getAuthenticator()); + AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticatorModel.getProviderId()); + Authenticator authenticator = factory.create(authenticatorModel); + Result context = new Result(model, authenticatorModel, authenticator); + authenticator.action(context); + + FlowExecution flowExecution = createFlowExecution(this.flowId); + Response challenge = flowExecution.action(execution, context); + if (challenge != null) return challenge; + if (clientSession.getAuthenticatedUser() == null) { + throw new AuthException(Error.UNKNOWN_USER); + } + return authenticationComplete(); + } + public void checkClientSession() { ClientSessionCode code = new ClientSessionCode(realm, clientSession); if (!code.isValidAction(ClientSessionModel.Action.AUTHENTICATE.name())) { @@ -457,6 +512,7 @@ public class AuthenticationProcessor { if (!code.isActionActive(ClientSessionModel.Action.AUTHENTICATE.name())) { throw new AuthException(Error.EXPIRED_CODE); } + clientSession.setTimestamp(Time.currentTime()); } public Response authenticateOnly() throws AuthException { @@ -566,14 +622,14 @@ public class AuthenticationProcessor { continue; } if (model.isAlternative() && alternativeSuccessful) { - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); + clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED); continue; } if (model.isAutheticatorFlow()) { FlowExecution flowExecution = createFlowExecution(model.getAuthenticator()); Response flowResponse = flowExecution.processFlow(); if (flowResponse == null) { - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS); + clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS); if (model.isAlternative()) alternativeSuccessful = true; continue; } else { @@ -590,7 +646,7 @@ public class AuthenticationProcessor { if (authenticator.requiresUser() && authUser == null){ if (alternativeChallenge != null) { - clientSession.setAuthenticatorStatus(challengedAlternativeExecution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); + clientSession.setExecutionStatus(challengedAlternativeExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); return alternativeChallenge; } throw new AuthException("authenticator: " + authenticatorModel.getProviderId(), Error.UNKNOWN_USER); @@ -602,14 +658,14 @@ public class AuthenticationProcessor { if (model.isRequired()) { if (model.isUserSetupAllowed()) { logger.debugv("authenticator SETUP_REQUIRED: {0}", authenticatorModel.getProviderId()); - clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED); + 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.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); + clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED); continue; } } @@ -629,47 +685,49 @@ public class AuthenticationProcessor { Status status = result.getStatus(); if (status == Status.SUCCESS){ logger.debugv("authenticator SUCCESS: {0}", authenticatorModel.getProviderId()); - clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS); + clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS); if (execution.isAlternative()) alternativeSuccessful = true; return null; } else if (status == Status.FAILED) { logger.debugv("authenticator FAILED: {0}", authenticatorModel.getProviderId()); - logUserFailure(); - clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.FAILED); - if (result.challenge != null) return result.challenge; + 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.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); - return result.challenge; + clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); + return sendChallenge(result, execution); } else if (status == Status.CHALLENGE) { logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId()); if (execution.isRequired()) { - clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); - return result.challenge; + 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.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); - return result.challenge; + clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); + return sendChallenge(result, execution); } if (execution.isAlternative()) { alternativeChallenge = result.challenge; challengedAlternativeExecution = execution; } else { - clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED); + clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED); } return null; } else if (status == Status.FAILURE_CHALLENGE) { logger.debugv("authenticator FAILURE_CHALLENGE: {0}", authenticatorModel.getProviderId()); - logUserFailure(); - clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.CHALLENGED); - return result.challenge; + logFailure(); + clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED); + return sendChallenge(result, execution); } else if (status == Status.ATTEMPTED) { logger.debugv("authenticator ATTEMPTED: {0}", authenticatorModel.getProviderId()); if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) { throw new AuthException(Error.INVALID_CREDENTIALS); } - clientSession.setAuthenticatorStatus(execution.getId(), UserSessionModel.AuthenticatorStatus.ATTEMPTED); + clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED); return null; } else { logger.debugv("authenticator INTERNAL_ERROR: {0}", authenticatorModel.getProviderId()); @@ -679,8 +737,13 @@ public class AuthenticationProcessor { } + public Response sendChallenge(Result result, AuthenticationExecutionModel execution) { + clientSession.setNote(CURRENT_AUTHENTICATION_EXECUTION, execution.getId()); + return result.challenge; + } - } + + } diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java index 8245f82fe7..36e6d52b7f 100755 --- a/services/src/main/java/org/keycloak/authentication/Authenticator.java +++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java @@ -20,5 +20,7 @@ public interface Authenticator extends Provider { */ void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user); + void action(AuthenticatorContext context); + } diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator2.java b/services/src/main/java/org/keycloak/authentication/Authenticator2.java deleted file mode 100755 index f78d2839e9..0000000000 --- a/services/src/main/java/org/keycloak/authentication/Authenticator2.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.keycloak.authentication; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.provider.Provider; -import sun.security.krb5.internal.AuthContext; - -/** -* @author Bill Burke -* @version $Revision: 1 $ -*/ -public interface Authenticator2 extends Authenticator { - void putAction(AuthContext context); - void postAction(AuthContext context); - void deleteAction(AuthContext context); - void getAction(AuthContext context); -} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java index 6c611b1542..957c5407fe 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java @@ -2,6 +2,7 @@ package org.keycloak.authentication.authenticators; import org.keycloak.OAuth2Constants; import org.keycloak.authentication.AuthenticationProcessor; +import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.AuthenticatorContext; import org.keycloak.events.Details; import org.keycloak.events.Errors; @@ -24,20 +25,25 @@ import java.util.List; * @author Bill Burke * @version $Revision: 1 $ */ -public class AbstractFormAuthenticator { +public abstract class AbstractFormAuthenticator implements Authenticator { - public static final String LOGIN_FORM_ACTION = "login_form"; public static final String REGISTRATION_FORM_ACTION = "registration_form"; - public static final String ACTION = "action"; - public static final String FORM_USERNAME = "FORM_USERNAME"; + public static final String EXECUTION = "execution"; + public static final String ATTEMPTED_USERNAME = "ATTEMPTED_USERNAME"; + + @Override + public void action(AuthenticatorContext context) { + + } + + @Override + public void close() { - protected boolean isAction(AuthenticatorContext context, String action) { - return action.equals(context.getAction()); } protected LoginFormsProvider loginForm(AuthenticatorContext context) { String accessCode = context.generateAccessCode(); - URI action = getActionUrl(context, accessCode, LOGIN_FORM_ACTION); + URI action = getActionUrl(context, accessCode); LoginFormsProvider provider = context.getSession().getProvider(LoginFormsProvider.class) .setUser(context.getUser()) .setActionUri(action) @@ -48,10 +54,10 @@ public class AbstractFormAuthenticator { return provider; } - public static URI getActionUrl(AuthenticatorContext context, String code, String action) { + public URI getActionUrl(AuthenticatorContext context, String code) { return LoginActionsService.authenticationFormProcessor(context.getUriInfo()) .queryParam(OAuth2Constants.CODE, code) - .queryParam(ACTION, action) + .queryParam(EXECUTION, context.getExecution().getId()) .build(context.getRealm().getName()); } @@ -111,7 +117,7 @@ public class AbstractFormAuthenticator { return false; } context.getEvent().detail(Details.USERNAME, username); - context.getClientSession().setNote(AbstractFormAuthenticator.FORM_USERNAME, username); + context.getClientSession().setNote(AbstractFormAuthenticator.ATTEMPTED_USERNAME, username); UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username); if (invalidUser(context, user)) return false; String rememberMe = inputData.getFirst("rememberMe"); @@ -119,6 +125,8 @@ public class AbstractFormAuthenticator { if (remember) { context.getClientSession().setNote(Details.REMEMBER_ME, "true"); context.getEvent().detail(Details.REMEMBER_ME, "true"); + } else { + context.getClientSession().removeNote(Details.REMEMBER_ME); } context.setUser(user); return true; diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java index a4d64301d5..7e68a0221c 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java @@ -32,6 +32,11 @@ public class CookieAuthenticator implements Authenticator { } + @Override + public void action(AuthenticatorContext context) { + + } + @Override public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { return true; diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java index bce060b4da..fd2aa08b69 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java @@ -32,15 +32,16 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A } @Override - public void authenticate(AuthenticatorContext context) { - if (!isAction(context, TOTP_FORM_ACTION)) { - Response challengeResponse = challenge(context, null); - context.challenge(challengeResponse); - return; - } + public void action(AuthenticatorContext context) { validateOTP(context); } + @Override + public void authenticate(AuthenticatorContext context) { + Response challengeResponse = challenge(context, null); + context.challenge(challengeResponse); + } + public void validateOTP(AuthenticatorContext context) { MultivaluedMap inputData = context.getHttpRequest().getDecodedFormParameters(); List credentials = new LinkedList<>(); @@ -69,7 +70,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A protected Response challenge(AuthenticatorContext context, String error) { String accessCode = context.generateAccessCode(); - URI action = AbstractFormAuthenticator.getActionUrl(context, accessCode, TOTP_FORM_ACTION); + URI action = getActionUrl(context, accessCode); LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class) .setActionUri(action) .setClientSessionCode(accessCode); @@ -91,6 +92,8 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A } + + @Override public void close() { diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java index eb6c1eeea0..0696e063dc 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/SpnegoAuthenticator.java @@ -39,26 +39,17 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au return false; } - protected boolean isAlreadyChallenged(AuthenticatorContext context) { - UserSessionModel.AuthenticatorStatus status = context.getClientSession().getAuthenticators().get(context.getExecution().getId()); - if (status == null) return false; - return status == UserSessionModel.AuthenticatorStatus.CHALLENGED; + @Override + public void action(AuthenticatorContext context) { + context.attempted(); + return; } @Override public void authenticate(AuthenticatorContext context) { HttpRequest request = context.getHttpRequest(); String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); - if (isAction(context, KERBEROS_DISABLED)) { - context.attempted(); - return; - } - // Case when we don't yet have any Negotiate header if (authHeader == null) { - if (isAlreadyChallenged(context)) { - context.attempted(); - return; - } Response challenge = challengeNegotiation(context, null); context.forceChallenge(challenge); return; @@ -131,7 +122,7 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au */ protected Response optionalChallengeRedirect(AuthenticatorContext context, String negotiateHeader) { String accessCode = context.generateAccessCode(); - URI action = getActionUrl(context, accessCode, KERBEROS_DISABLED); + URI action = getActionUrl(context, accessCode); StringBuilder builder = new StringBuilder(); @@ -159,18 +150,6 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au .entity(builder.toString()).build(); } - protected Response formChallenge(AuthenticatorContext context, String negotiateHeader) { - String accessCode = context.generateAccessCode(); - URI action = getActionUrl(context, accessCode, KERBEROS_DISABLED); - return context.getSession().getProvider(LoginFormsProvider.class) - .setClientSessionCode(accessCode) - .setActionUri(action) - .setStatus(Response.Status.UNAUTHORIZED) - .setResponseHeader(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader) - .setUser(context.getUser()) - .createForm("bypass_kerberos.ftl", new HashMap()); - } - @Override public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java index 3dd81d75fb..2c25bacac0 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/UsernamePasswordForm.java @@ -4,24 +4,18 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl; import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.AuthenticatorContext; -import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.login.LoginFormsProvider; import org.keycloak.models.AuthenticatorModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; -import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol; -import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.AuthenticationManager; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import java.util.LinkedList; -import java.util.List; /** * @author Bill Burke @@ -34,30 +28,8 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A this.model = model; } - @Override - public void authenticate(AuthenticatorContext context) { - if (isAction(context, REGISTRATION_FORM_ACTION) && context.getUser() != null) { - context.success(); - return; - } - if (!isAction(context, LOGIN_FORM_ACTION)) { - MultivaluedMap formData = new MultivaluedMapImpl<>(); - String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM); - - String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders()); - - if (loginHint != null || rememberMeUsername != null) { - if (loginHint != null) { - formData.add(AuthenticationManager.FORM_USERNAME, loginHint); - } else { - formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername); - formData.add("rememberMe", "on"); - } - } - Response challengeResponse = challenge(context, formData); - context.challenge(challengeResponse); - return; - } + @Override + public void action(AuthenticatorContext context) { MultivaluedMap formData = context.getHttpRequest().getDecodedFormParameters(); if (formData.containsKey("cancel")) { context.getEvent().error(Errors.REJECTED_BY_USER); @@ -66,10 +38,9 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A .setHttpHeaders(context.getHttpRequest().getHttpHeaders()) .setUriInfo(context.getUriInfo()); Response response = protocol.cancelLogin(context.getClientSession()); - context.challenge(response); + context.forceChallenge(response); return; } - if (!validateUser(context, formData)) { return; } @@ -77,7 +48,30 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A return; } context.success(); + } + @Override + public void authenticate(AuthenticatorContext context) { + if (REGISTRATION_FORM_ACTION.equals(context.getAction()) && context.getUser() != null) { + context.success(); + return; + } + MultivaluedMap formData = new MultivaluedMapImpl<>(); + String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM); + + String rememberMeUsername = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders()); + + if (loginHint != null || rememberMeUsername != null) { + if (loginHint != null) { + formData.add(AuthenticationManager.FORM_USERNAME, loginHint); + } else { + formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername); + formData.add("rememberMe", "on"); + } + } + Response challengeResponse = challenge(context, formData); + context.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, context.getExecution().getId()); + context.challenge(challengeResponse); } @Override diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java index d203218048..b19295bb36 100755 --- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java +++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java @@ -142,25 +142,6 @@ public class ClientSessionCode { clientSession.setTimestamp(Time.currentTime()); } - public void setRequiredAction(RequiredAction requiredAction) { - setAction(convertToAction(requiredAction)); - } - - private String convertToAction(RequiredAction requiredAction) { - switch (requiredAction) { - case CONFIGURE_TOTP: - return ClientSessionModel.Action.CONFIGURE_TOTP.name(); - case UPDATE_PASSWORD: - return ClientSessionModel.Action.UPDATE_PASSWORD.name(); - case UPDATE_PROFILE: - return ClientSessionModel.Action.UPDATE_PROFILE.name(); - case VERIFY_EMAIL: - return ClientSessionModel.Action.VERIFY_EMAIL.name(); - default: - throw new IllegalArgumentException("Unknown required action " + requiredAction); - } - } - public String getCode() { return generateCode(realm, clientSession); } diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 3bec193492..cca6bb0534 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -23,6 +23,7 @@ package org.keycloak.services.resources; import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.InternalServerErrorException; import org.keycloak.ClientConnection; import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.AuthenticatorUtil; @@ -65,6 +66,7 @@ import org.keycloak.services.ErrorPage; import org.keycloak.services.Urls; import org.keycloak.services.util.CookieHelper; import org.keycloak.services.validation.Validation; +import org.keycloak.util.Time; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -234,7 +236,7 @@ public class LoginActionsService { @Path("authenticate") @GET public Response authenticate(@QueryParam("code") String code, - @QueryParam("action") String action) { + @QueryParam("execution") String execution) { event.event(EventType.LOGIN); Checks checks = new Checks(); if (!checks.check(code)) { @@ -249,27 +251,57 @@ public class LoginActionsService { clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); } - AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); - String flowId = flow.getId(); + return processAuthentication(execution, clientSession); + } + + protected Response processAuthentication(String execution, ClientSessionModel clientSession) { + String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW; + AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias); AuthenticationProcessor processor = new AuthenticationProcessor(); processor.setClientSession(clientSession) - .setFlowId(flowId) + .setFlowId(flow.getId()) .setConnection(clientConnection) .setEventBuilder(event) .setProtector(authManager.getProtector()) - .setAction(action) .setRealm(realm) .setSession(session) .setUriInfo(uriInfo) .setRequest(request); try { - return processor.authenticate(); + if (execution != null) { + return processor.authenticationAction(execution); + } else { + return processor.authenticate(); + } } catch (Exception e) { return processor.handleBrowserException(e); } } + /** + * URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY! + * + * @param code + * @return + */ + @Path("authenticate") + @POST + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public Response authenticateForm(@QueryParam("code") String code, + @QueryParam("execution") String execution) { + event.event(EventType.LOGIN); + Checks checks = new Checks(); + if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) { + return checks.response; + } + final ClientSessionCode clientCode = checks.clientCode; + final ClientSessionModel clientSession = clientCode.getClientSession(); + + return processAuthentication(execution, clientSession); + } + + /** * protocol independent registration page entry point * @@ -302,46 +334,6 @@ public class LoginActionsService { .createRegistration(); } - /** - * URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY! - * - * @param code - * @return - */ - @Path("authenticate") - @POST - @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response authenticateForm(@QueryParam("code") String code, - @QueryParam("action") String action) { - event.event(EventType.LOGIN); - Checks checks = new Checks(); - if (!checks.check(code, ClientSessionModel.Action.AUTHENTICATE.name())) { - return checks.response; - } - final ClientSessionCode clientCode = checks.clientCode; - final ClientSessionModel clientSession = clientCode.getClientSession(); - - String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW; - AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias); - AuthenticationProcessor processor = new AuthenticationProcessor(); - processor.setClientSession(clientSession) - .setFlowId(flow.getId()) - .setConnection(clientConnection) - .setEventBuilder(event) - .setProtector(authManager.getProtector()) - .setRealm(realm) - .setSession(session) - .setUriInfo(uriInfo) - .setAction(action) - .setRequest(request); - - try { - return processor.authenticate(); - } catch (Exception e) { - return processor.handleBrowserException(e); - } - - } /** * Registration @@ -946,7 +938,7 @@ public class LoginActionsService { } @Path("required-actions/{action}") - public Object requiredAction(@QueryParam("code") String code, + public Object requiredAction(@QueryParam("code") final String code, @PathParam("action") String action) { event.event(EventType.LOGIN); if (action == null) { @@ -1024,6 +1016,11 @@ public class LoginActionsService { @Override public String generateAccessCode(String action) { + String clientSessionAction = clientSession.getAction(); + if (action.equals(clientSessionAction)) { + clientSession.setTimestamp(Time.currentTime()); + return code; + } ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession()); code.setAction(action); return code.getCode();