Merge pull request #1404 from patriot1burke/master

handle page refresh better
This commit is contained in:
Bill Burke 2015-06-18 20:16:29 -04:00
commit 8d885d14ed
19 changed files with 450 additions and 319 deletions

View file

@ -32,9 +32,9 @@ public interface ClientSessionModel {
public Set<String> getProtocolMappers(); public Set<String> getProtocolMappers();
public void setProtocolMappers(Set<String> protocolMappers); public void setProtocolMappers(Set<String> protocolMappers);
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators(); public Map<String, ExecutionStatus> getExecutionStatus();
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status); public void setExecutionStatus(String authenticator, ExecutionStatus status);
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status); public void clearExecutionStatus();
public UserModel getAuthenticatedUser(); public UserModel getAuthenticatedUser();
public void setAuthenticatedUser(UserModel user); public void setAuthenticatedUser(UserModel user);
@ -67,6 +67,8 @@ public interface ClientSessionModel {
*/ */
public Map<String, String> getUserSessionNotes(); public Map<String, String> getUserSessionNotes();
public void clearUserSessionNotes();
public static enum Action { public static enum Action {
OAUTH_GRANT, OAUTH_GRANT,
CODE_TO_TOKEN, CODE_TO_TOKEN,
@ -80,4 +82,12 @@ public interface ClientSessionModel {
LOGGED_OUT LOGGED_OUT
} }
public enum ExecutionStatus {
FAILED,
SUCCESS,
SETUP_REQUIRED,
ATTEMPTED,
SKIPPED,
CHALLENGED
}
} }

View file

@ -37,15 +37,6 @@ public interface UserSessionModel {
List<ClientSessionModel> getClientSessions(); List<ClientSessionModel> getClientSessions();
public static enum AuthenticatorStatus {
FAILED,
SUCCESS,
SETUP_REQUIRED,
ATTEMPTED,
SKIPPED,
CHALLENGED
}
public String getNote(String name); public String getNote(String name);
public void setNote(String name, String value); public void setNote(String name, String value);
public void removeNote(String name); public void removeNote(String name);

View file

@ -185,23 +185,32 @@ public class ClientSessionAdapter implements ClientSessionModel {
return copy; return copy;
} }
@Override
public void clearUserSessionNotes() {
entity.setUserSessionNotes(new HashMap<String, String>());
update();
}
void update() { void update() {
provider.getTx().replace(cache, entity.getId(), entity); provider.getTx().replace(cache, entity.getId(), entity);
} }
@Override @Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() { public Map<String, ExecutionStatus> getExecutionStatus() {
return entity.getAuthenticatorStatus(); return entity.getAuthenticatorStatus();
} }
@Override @Override
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { public void setExecutionStatus(String authenticator, ExecutionStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status); entity.getAuthenticatorStatus().put(authenticator, status);
update();
} }
@Override @Override
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) { public void clearExecutionStatus() {
entity.setAuthenticatorStatus(status); entity.getAuthenticatorStatus().clear();
update();
} }
@Override @Override
@ -211,6 +220,7 @@ public class ClientSessionAdapter implements ClientSessionModel {
@Override @Override
public void setAuthenticatedUser(UserModel user) { public void setAuthenticatedUser(UserModel user) {
entity.setAuthUserId(user.getId()); entity.setAuthUserId(user.getId());
update();
} }

View file

@ -1,7 +1,6 @@
package org.keycloak.models.sessions.infinispan.entities; package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserSessionModel;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -30,7 +29,7 @@ public class ClientSessionEntity extends SessionEntity {
private Set<String> protocolMappers; private Set<String> protocolMappers;
private Map<String, String> notes; private Map<String, String> notes;
private Map<String, String> userSessionNotes; private Map<String, String> userSessionNotes;
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>(); private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
private String authUserId; private String authUserId;
public String getClient() { public String getClient() {
@ -113,11 +112,11 @@ public class ClientSessionEntity extends SessionEntity {
this.notes = notes; this.notes = notes;
} }
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() { public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
return authenticatorStatus; return authenticatorStatus;
} }
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) { public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus; this.authenticatorStatus = authenticatorStatus;
} }

View file

@ -6,6 +6,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; 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.ClientSessionEntity;
import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity; import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity;
import org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity; import org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity;
@ -106,6 +107,17 @@ public class ClientSessionAdapter implements ClientSessionModel {
return copy; return copy;
} }
@Override
public void clearUserSessionNotes() {
Iterator<ClientUserSessionNoteEntity> it = entity.getUserSessionNotes().iterator();
while (it.hasNext()) {
ClientUserSessionNoteEntity attr = it.next();
it.remove();
em.remove(attr);
}
}
@Override @Override
public String getId() { public String getId() {
return entity.getId(); return entity.getId();
@ -242,27 +254,44 @@ public class ClientSessionAdapter implements ClientSessionModel {
} }
@Override @Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() { public Map<String, ExecutionStatus> getExecutionStatus() {
return null; Map<String, ExecutionStatus> result = new HashMap<>();
for (ClientSessionAuthStatusEntity status : entity.getAuthanticatorStatus()) {
result.put(status.getAuthenticator(), status.getStatus());
}
return result;
} }
@Override @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 @Override
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) { public void clearExecutionStatus() {
Iterator<ClientSessionAuthStatusEntity> iterator = entity.getAuthanticatorStatus().iterator();
while (iterator.hasNext()) {
ClientSessionAuthStatusEntity authStatus = iterator.next();
iterator.remove();
em.remove(authStatus);
}
} }
@Override @Override
public UserModel getAuthenticatedUser() { public UserModel getAuthenticatedUser() {
return null; return session.users().getUserById(entity.getUserId(), realm);
} }
@Override @Override
public void setAuthenticatedUser(UserModel user) { public void setAuthenticatedUser(UserModel user) {
entity.setUserId(user.getId());
} }
} }

View file

@ -1,6 +1,6 @@
package org.keycloak.models.sessions.jpa.entities; package org.keycloak.models.sessions.jpa.entities;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.ClientSessionModel;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -39,7 +39,7 @@ public class ClientSessionAuthStatusEntity {
@Column(name = "AUTHENTICATOR") @Column(name = "AUTHENTICATOR")
protected String authenticator; protected String authenticator;
@Column(name = "STATUS") @Column(name = "STATUS")
protected UserSessionModel.AuthenticatorStatus status; protected ClientSessionModel.ExecutionStatus status;
public String getAuthenticator() { public String getAuthenticator() {
return authenticator; return authenticator;
@ -49,11 +49,11 @@ public class ClientSessionAuthStatusEntity {
this.authenticator = authenticator; this.authenticator = authenticator;
} }
public UserSessionModel.AuthenticatorStatus getStatus() { public ClientSessionModel.ExecutionStatus getStatus() {
return status; return status;
} }
public void setStatus(UserSessionModel.AuthenticatorStatus status) { public void setStatus(ClientSessionModel.ExecutionStatus status) {
this.status = status; this.status = status;
} }

View file

@ -155,19 +155,24 @@ public class ClientSessionAdapter implements ClientSessionModel {
} }
@Override @Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() { public Map<String, ExecutionStatus> getExecutionStatus() {
return entity.getAuthenticatorStatus(); return entity.getAuthenticatorStatus();
} }
@Override @Override
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { public void setExecutionStatus(String authenticator, ExecutionStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status); entity.getAuthenticatorStatus().put(authenticator, status);
} }
@Override @Override
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) { public void clearExecutionStatus() {
entity.setAuthenticatorStatus(status); entity.getAuthenticatorStatus().clear();
}
@Override
public void clearUserSessionNotes() {
entity.getUserSessionNotes().clear();
} }
@Override @Override

View file

@ -1,7 +1,6 @@
package org.keycloak.models.sessions.mem.entities; package org.keycloak.models.sessions.mem.entities;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserSessionModel;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -15,7 +14,7 @@ public class ClientSessionEntity {
private String id; private String id;
private String clientId; private String clientId;
private String realmId; private String realmId;
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>(); private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
private String authUserId; private String authUserId;
private UserSessionEntity session; private UserSessionEntity session;
@ -122,11 +121,11 @@ public class ClientSessionEntity {
this.authUserId = authUserId; this.authUserId = authUserId;
} }
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() { public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
return authenticatorStatus; return authenticatorStatus;
} }
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) { public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus; this.authenticatorStatus = authenticatorStatus;
} }

View file

@ -171,22 +171,26 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
} }
@Override @Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() { public Map<String, ExecutionStatus> getExecutionStatus() {
return entity.getAuthenticatorStatus(); return entity.getAuthenticatorStatus();
} }
@Override @Override
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { public void setExecutionStatus(String authenticator, ExecutionStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status); entity.getAuthenticatorStatus().put(authenticator, status);
updateMongoEntity(); updateMongoEntity();
} }
@Override @Override
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) { public void clearExecutionStatus() {
entity.setAuthenticatorStatus(status); entity.getAuthenticatorStatus().clear();
updateMongoEntity(); updateMongoEntity();
}
@Override
public void clearUserSessionNotes() {
entity.getUserSessionNotes().clear();
} }
@Override @Override

View file

@ -4,7 +4,6 @@ import org.keycloak.connections.mongo.api.MongoCollection;
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity; import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.entities.AbstractIdentifiableEntity; import org.keycloak.models.entities.AbstractIdentifiableEntity;
import java.util.HashMap; import java.util.HashMap;
@ -31,7 +30,7 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
private List<String> protocolMappers; private List<String> protocolMappers;
private Map<String, String> notes = new HashMap<String, String>(); private Map<String, String> notes = new HashMap<String, String>();
private Map<String, String> userSessionNotes = new HashMap<String, String>(); private Map<String, String> userSessionNotes = new HashMap<String, String>();
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>(); private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
private String authUserId; private String authUserId;
public String getId() { public String getId() {
@ -130,11 +129,11 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() { public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
return authenticatorStatus; return authenticatorStatus;
} }
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) { public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus; this.authenticatorStatus = authenticatorStatus;
} }

View file

@ -3,6 +3,7 @@ package org.keycloak.authentication;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection; import org.keycloak.ClientConnection;
import org.keycloak.authentication.authenticators.AbstractFormAuthenticator;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
@ -21,17 +22,19 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.BruteForceProtector; import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.util.Time;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import java.util.Collections; import java.util.HashMap;
import java.util.List; import java.util.Iterator;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class AuthenticationProcessor { public class AuthenticationProcessor {
public static final String CURRENT_AUTHENTICATION_EXECUTION = "current.authentication.execution";
protected static Logger logger = Logger.getLogger(AuthenticationProcessor.class); protected static Logger logger = Logger.getLogger(AuthenticationProcessor.class);
protected RealmModel realm; protected RealmModel realm;
protected UserSessionModel userSession; protected UserSessionModel userSession;
@ -323,7 +326,7 @@ public class AuthenticationProcessor {
@Override @Override
public String generateAccessCode() { public String generateAccessCode() {
ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession()); ClientSessionCode accessCode = new ClientSessionCode(getRealm(), getClientSession());
accessCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setTimestamp(Time.currentTime());
return accessCode.getCode(); return accessCode.getCode();
} }
} }
@ -360,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) { protected boolean isProcessed(AuthenticationExecutionModel model) {
if (model.isDisabled()) return true; 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; if (status == null) return false;
return status == UserSessionModel.AuthenticatorStatus.SUCCESS || status == UserSessionModel.AuthenticatorStatus.SKIPPED return status == ClientSessionModel.ExecutionStatus.SUCCESS || status == ClientSessionModel.ExecutionStatus.SKIPPED
|| status == UserSessionModel.AuthenticatorStatus.ATTEMPTED || status == ClientSessionModel.ExecutionStatus.ATTEMPTED
|| status == UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED; || status == ClientSessionModel.ExecutionStatus.SETUP_REQUIRED;
} }
public boolean isSuccessful(AuthenticationExecutionModel model) { 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; if (status == null) return false;
return status == UserSessionModel.AuthenticatorStatus.SUCCESS; return status == ClientSessionModel.ExecutionStatus.SUCCESS;
} }
public Response handleBrowserException(Exception failure) { public Response handleBrowserException(Exception failure) {
@ -414,6 +426,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 { public Response authenticate() throws AuthException {
checkClientSession(); checkClientSession();
@ -428,7 +450,53 @@ public class AuthenticationProcessor {
} }
UserModel authUser = clientSession.getAuthenticatedUser(); UserModel authUser = clientSession.getAuthenticatedUser();
validateUser(authUser); 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);
}
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 (challenge != null) return challenge;
if (clientSession.getAuthenticatedUser() == null) { if (clientSession.getAuthenticatedUser() == null) {
throw new AuthException(Error.UNKNOWN_USER); throw new AuthException(Error.UNKNOWN_USER);
@ -444,6 +512,7 @@ public class AuthenticationProcessor {
if (!code.isActionActive(ClientSessionModel.Action.AUTHENTICATE.name())) { if (!code.isActionActive(ClientSessionModel.Action.AUTHENTICATE.name())) {
throw new AuthException(Error.EXPIRED_CODE); throw new AuthException(Error.EXPIRED_CODE);
} }
clientSession.setTimestamp(Time.currentTime());
} }
public Response authenticateOnly() throws AuthException { public Response authenticateOnly() throws AuthException {
@ -458,7 +527,8 @@ public class AuthenticationProcessor {
} }
UserModel authUser = clientSession.getAuthenticatedUser(); UserModel authUser = clientSession.getAuthenticatedUser();
validateUser(authUser); validateUser(authUser);
Response challenge = processFlow(flowId); FlowExecution flowExecution = createFlowExecution(this.flowId);
Response challenge = flowExecution.processFlow();
if (challenge != null) return challenge; if (challenge != null) return challenge;
String username = clientSession.getAuthenticatedUser().getUsername(); String username = clientSession.getAuthenticatedUser().getUsername();
@ -482,125 +552,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<AuthenticationExecutionModel> 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) { public void validateUser(UserModel authenticatedUser) {
if (authenticatedUser != null) { if (authenticatedUser != null) {
if (!authenticatedUser.isEnabled()) throw new AuthException(Error.USER_DISABLED); if (!authenticatedUser.isEnabled()) throw new AuthException(Error.USER_DISABLED);
@ -632,5 +583,170 @@ public class AuthenticationProcessor {
} }
class FlowExecution {
Response alternativeChallenge = null;
AuthenticationExecutionModel challengedAlternativeExecution = null;
boolean alternativeSuccessful = false;
Iterator<AuthenticationExecutionModel> executions;
public Response action(String actionExecution, Result actionResult) {
while (executions.hasNext()) {
AuthenticationExecutionModel model = executions.next();
if (isProcessed(model)) {
logger.debug("execution is processed");
if (!alternativeSuccessful && model.isAlternative() && isSuccessful(model)) alternativeSuccessful = true;
continue;
}
if (!model.getId().equals(actionExecution)) {
if (model.isAutheticatorFlow()) {
FlowExecution flowExecution = createFlowExecution(model.getAuthenticator());
return flowExecution.action(actionExecution, actionResult);
} else {
throw new AuthException("action is not current execution", Error.INTERNAL_ERROR);
}
} else { // we found the action
Response response = processResult(actionResult);
if (response == null) return processFlow();
else return response;
}
}
throw new AuthException("action is not in current execution", Error.INTERNAL_ERROR);
}
public Response processFlow() {
while (executions.hasNext()) {
AuthenticationExecutionModel model = executions.next();
if (isProcessed(model)) {
logger.debug("execution is processed");
if (!alternativeSuccessful && model.isAlternative() && isSuccessful(model)) alternativeSuccessful = true;
continue;
}
if (model.isAlternative() && alternativeSuccessful) {
clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
continue;
}
if (model.isAutheticatorFlow()) {
FlowExecution flowExecution = createFlowExecution(model.getAuthenticator());
Response flowResponse = flowExecution.processFlow();
if (flowResponse == null) {
clientSession.setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
if (model.isAlternative()) alternativeSuccessful = true;
continue;
} else {
return flowResponse;
}
}
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.setExecutionStatus(challengedAlternativeExecution.getId(), ClientSessionModel.ExecutionStatus.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.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, 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.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());
logFailure();
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
if (result.challenge != null) {
return sendChallenge(result, execution);
}
throw new AuthException(result.error);
} else if (status == Status.FORCE_CHALLENGE) {
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
} else if (status == Status.CHALLENGE) {
logger.debugv("authenticator CHALLENGE: {0}", authenticatorModel.getProviderId());
if (execution.isRequired()) {
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
UserModel authenticatedUser = clientSession.getAuthenticatedUser();
if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(session, realm, authenticatedUser)) {
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
if (execution.isAlternative()) {
alternativeChallenge = result.challenge;
challengedAlternativeExecution = execution;
} else {
clientSession.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
}
return null;
} else if (status == Status.FAILURE_CHALLENGE) {
logger.debugv("authenticator FAILURE_CHALLENGE: {0}", authenticatorModel.getProviderId());
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.setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
return null;
} else {
logger.debugv("authenticator INTERNAL_ERROR: {0}", authenticatorModel.getProviderId());
logger.error("Unknown result status");
throw new AuthException(Error.INTERNAL_ERROR);
}
}
public Response sendChallenge(Result result, AuthenticationExecutionModel execution) {
clientSession.setNote(CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
return result.challenge;
}
}
} }

View file

@ -20,5 +20,7 @@ public interface Authenticator extends Provider {
*/ */
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user); void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
void action(AuthenticatorContext context);
} }

View file

@ -2,6 +2,7 @@ package org.keycloak.authentication.authenticators;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorContext; import org.keycloak.authentication.AuthenticatorContext;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
@ -24,20 +25,25 @@ import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @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 REGISTRATION_FORM_ACTION = "registration_form";
public static final String ACTION = "action"; public static final String EXECUTION = "execution";
public static final String FORM_USERNAME = "FORM_USERNAME"; 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) { protected LoginFormsProvider loginForm(AuthenticatorContext context) {
String accessCode = context.generateAccessCode(); String accessCode = context.generateAccessCode();
URI action = getActionUrl(context, accessCode, LOGIN_FORM_ACTION); URI action = getActionUrl(context, accessCode);
LoginFormsProvider provider = context.getSession().getProvider(LoginFormsProvider.class) LoginFormsProvider provider = context.getSession().getProvider(LoginFormsProvider.class)
.setUser(context.getUser()) .setUser(context.getUser())
.setActionUri(action) .setActionUri(action)
@ -48,10 +54,10 @@ public class AbstractFormAuthenticator {
return provider; return provider;
} }
public static URI getActionUrl(AuthenticatorContext context, String code, String action) { public URI getActionUrl(AuthenticatorContext context, String code) {
return LoginActionsService.authenticationFormProcessor(context.getUriInfo()) return LoginActionsService.authenticationFormProcessor(context.getUriInfo())
.queryParam(OAuth2Constants.CODE, code) .queryParam(OAuth2Constants.CODE, code)
.queryParam(ACTION, action) .queryParam(EXECUTION, context.getExecution().getId())
.build(context.getRealm().getName()); .build(context.getRealm().getName());
} }
@ -111,7 +117,7 @@ public class AbstractFormAuthenticator {
return false; return false;
} }
context.getEvent().detail(Details.USERNAME, username); 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); UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username);
if (invalidUser(context, user)) return false; if (invalidUser(context, user)) return false;
String rememberMe = inputData.getFirst("rememberMe"); String rememberMe = inputData.getFirst("rememberMe");
@ -119,6 +125,8 @@ public class AbstractFormAuthenticator {
if (remember) { if (remember) {
context.getClientSession().setNote(Details.REMEMBER_ME, "true"); context.getClientSession().setNote(Details.REMEMBER_ME, "true");
context.getEvent().detail(Details.REMEMBER_ME, "true"); context.getEvent().detail(Details.REMEMBER_ME, "true");
} else {
context.getClientSession().removeNote(Details.REMEMBER_ME);
} }
context.setUser(user); context.setUser(user);
return true; return true;

View file

@ -32,6 +32,11 @@ public class CookieAuthenticator implements Authenticator {
} }
@Override
public void action(AuthenticatorContext context) {
}
@Override @Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
return true; return true;

View file

@ -31,14 +31,15 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
this.model = model; this.model = model;
} }
@Override
public void action(AuthenticatorContext context) {
validateOTP(context);
}
@Override @Override
public void authenticate(AuthenticatorContext context) { public void authenticate(AuthenticatorContext context) {
if (!isAction(context, TOTP_FORM_ACTION)) {
Response challengeResponse = challenge(context, null); Response challengeResponse = challenge(context, null);
context.challenge(challengeResponse); context.challenge(challengeResponse);
return;
}
validateOTP(context);
} }
public void validateOTP(AuthenticatorContext context) { public void validateOTP(AuthenticatorContext context) {
@ -69,7 +70,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
protected Response challenge(AuthenticatorContext context, String error) { protected Response challenge(AuthenticatorContext context, String error) {
String accessCode = context.generateAccessCode(); 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) LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
.setActionUri(action) .setActionUri(action)
.setClientSessionCode(accessCode); .setClientSessionCode(accessCode);
@ -91,6 +92,8 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
} }
@Override @Override
public void close() { public void close() {

View file

@ -39,26 +39,17 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
return false; return false;
} }
protected boolean isAlreadyChallenged(AuthenticatorContext context) { @Override
UserSessionModel.AuthenticatorStatus status = context.getClientSession().getAuthenticators().get(context.getExecution().getId()); public void action(AuthenticatorContext context) {
if (status == null) return false; context.attempted();
return status == UserSessionModel.AuthenticatorStatus.CHALLENGED; return;
} }
@Override @Override
public void authenticate(AuthenticatorContext context) { public void authenticate(AuthenticatorContext context) {
HttpRequest request = context.getHttpRequest(); HttpRequest request = context.getHttpRequest();
String authHeader = request.getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); 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 (authHeader == null) {
if (isAlreadyChallenged(context)) {
context.attempted();
return;
}
Response challenge = challengeNegotiation(context, null); Response challenge = challengeNegotiation(context, null);
context.forceChallenge(challenge); context.forceChallenge(challenge);
return; return;
@ -131,7 +122,7 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
*/ */
protected Response optionalChallengeRedirect(AuthenticatorContext context, String negotiateHeader) { protected Response optionalChallengeRedirect(AuthenticatorContext context, String negotiateHeader) {
String accessCode = context.generateAccessCode(); String accessCode = context.generateAccessCode();
URI action = getActionUrl(context, accessCode, KERBEROS_DISABLED); URI action = getActionUrl(context, accessCode);
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
@ -159,18 +150,6 @@ public class SpnegoAuthenticator extends AbstractFormAuthenticator implements Au
.entity(builder.toString()).build(); .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<String, Object>());
}
@Override @Override
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {

View file

@ -4,24 +4,18 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.Authenticator; import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorContext; import org.keycloak.authentication.AuthenticatorContext;
import org.keycloak.events.Details;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.login.LoginFormsProvider; import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.AuthenticatorModel; import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.AuthenticationManager;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.LinkedList;
import java.util.List;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -34,13 +28,34 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A
this.model = model; this.model = model;
} }
@Override
public void action(AuthenticatorContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (formData.containsKey("cancel")) {
context.getEvent().error(Errors.REJECTED_BY_USER);
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
.setUriInfo(context.getUriInfo());
Response response = protocol.cancelLogin(context.getClientSession());
context.forceChallenge(response);
return;
}
if (!validateUser(context, formData)) {
return;
}
if (!validatePassword(context, formData)) {
return;
}
context.success();
}
@Override @Override
public void authenticate(AuthenticatorContext context) { public void authenticate(AuthenticatorContext context) {
if (isAction(context, REGISTRATION_FORM_ACTION) && context.getUser() != null) { if (REGISTRATION_FORM_ACTION.equals(context.getAction()) && context.getUser() != null) {
context.success(); context.success();
return; return;
} }
if (!isAction(context, LOGIN_FORM_ACTION)) {
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>(); MultivaluedMap<String, String> formData = new MultivaluedMapImpl<>();
String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM); String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM);
@ -55,29 +70,8 @@ public class UsernamePasswordForm extends AbstractFormAuthenticator implements A
} }
} }
Response challengeResponse = challenge(context, formData); Response challengeResponse = challenge(context, formData);
context.getClientSession().setNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, context.getExecution().getId());
context.challenge(challengeResponse); context.challenge(challengeResponse);
return;
}
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
if (formData.containsKey("cancel")) {
context.getEvent().error(Errors.REJECTED_BY_USER);
LoginProtocol protocol = context.getSession().getProvider(LoginProtocol.class, context.getClientSession().getAuthMethod());
protocol.setRealm(context.getRealm())
.setHttpHeaders(context.getHttpRequest().getHttpHeaders())
.setUriInfo(context.getUriInfo());
Response response = protocol.cancelLogin(context.getClientSession());
context.challenge(response);
return;
}
if (!validateUser(context, formData)) {
return;
}
if (!validatePassword(context, formData)) {
return;
}
context.success();
} }
@Override @Override

View file

@ -142,25 +142,6 @@ public class ClientSessionCode {
clientSession.setTimestamp(Time.currentTime()); 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() { public String getCode() {
return generateCode(realm, clientSession); return generateCode(realm, clientSession);
} }

View file

@ -23,6 +23,7 @@ package org.keycloak.services.resources;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.InternalServerErrorException;
import org.keycloak.ClientConnection; import org.keycloak.ClientConnection;
import org.keycloak.authentication.AuthenticationProcessor; import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.AuthenticatorUtil; import org.keycloak.authentication.AuthenticatorUtil;
@ -65,6 +66,7 @@ import org.keycloak.services.ErrorPage;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
import org.keycloak.services.util.CookieHelper; import org.keycloak.services.util.CookieHelper;
import org.keycloak.services.validation.Validation; import org.keycloak.services.validation.Validation;
import org.keycloak.util.Time;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.GET; import javax.ws.rs.GET;
@ -234,7 +236,7 @@ public class LoginActionsService {
@Path("authenticate") @Path("authenticate")
@GET @GET
public Response authenticate(@QueryParam("code") String code, public Response authenticate(@QueryParam("code") String code,
@QueryParam("action") String action) { @QueryParam("execution") String execution) {
event.event(EventType.LOGIN); event.event(EventType.LOGIN);
Checks checks = new Checks(); Checks checks = new Checks();
if (!checks.check(code)) { if (!checks.check(code)) {
@ -249,27 +251,57 @@ public class LoginActionsService {
clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
} }
AuthenticationFlowModel flow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); return processAuthentication(execution, clientSession);
String flowId = flow.getId(); }
protected Response processAuthentication(String execution, ClientSessionModel clientSession) {
String flowAlias = DefaultAuthenticationFlows.BROWSER_FLOW;
AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias);
AuthenticationProcessor processor = new AuthenticationProcessor(); AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setClientSession(clientSession) processor.setClientSession(clientSession)
.setFlowId(flowId) .setFlowId(flow.getId())
.setConnection(clientConnection) .setConnection(clientConnection)
.setEventBuilder(event) .setEventBuilder(event)
.setProtector(authManager.getProtector()) .setProtector(authManager.getProtector())
.setAction(action)
.setRealm(realm) .setRealm(realm)
.setSession(session) .setSession(session)
.setUriInfo(uriInfo) .setUriInfo(uriInfo)
.setRequest(request); .setRequest(request);
try { try {
if (execution != null) {
return processor.authenticationAction(execution);
} else {
return processor.authenticate(); return processor.authenticate();
}
} catch (Exception e) { } catch (Exception e) {
return processor.handleBrowserException(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 * protocol independent registration page entry point
* *
@ -302,46 +334,6 @@ public class LoginActionsService {
.createRegistration(); .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 * Registration
@ -946,7 +938,7 @@ public class LoginActionsService {
} }
@Path("required-actions/{action}") @Path("required-actions/{action}")
public Object requiredAction(@QueryParam("code") String code, public Object requiredAction(@QueryParam("code") final String code,
@PathParam("action") String action) { @PathParam("action") String action) {
event.event(EventType.LOGIN); event.event(EventType.LOGIN);
if (action == null) { if (action == null) {
@ -1024,6 +1016,11 @@ public class LoginActionsService {
@Override @Override
public String generateAccessCode(String action) { 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()); ClientSessionCode code = new ClientSessionCode(getRealm(), getClientSession());
code.setAction(action); code.setAction(action);
return code.getCode(); return code.getCode();