Merge pull request #1404 from patriot1burke/master
handle page refresh better
This commit is contained in:
commit
8d885d14ed
19 changed files with 450 additions and 319 deletions
|
@ -32,9 +32,9 @@ public interface ClientSessionModel {
|
|||
public Set<String> getProtocolMappers();
|
||||
public void setProtocolMappers(Set<String> protocolMappers);
|
||||
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators();
|
||||
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status);
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status);
|
||||
public Map<String, ExecutionStatus> 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<String, String> 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,15 +37,6 @@ public interface UserSessionModel {
|
|||
|
||||
List<ClientSessionModel> 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);
|
||||
|
|
|
@ -185,23 +185,32 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
return copy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUserSessionNotes() {
|
||||
entity.setUserSessionNotes(new HashMap<String, String>());
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
void update() {
|
||||
provider.getTx().replace(cache, entity.getId(), entity);
|
||||
}
|
||||
@Override
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
public Map<String, ExecutionStatus> 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<String, UserSessionModel.AuthenticatorStatus> 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();
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String> protocolMappers;
|
||||
private Map<String, String> notes;
|
||||
private Map<String, String> userSessionNotes;
|
||||
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
|
||||
private String authUserId;
|
||||
|
||||
public String getClient() {
|
||||
|
@ -113,11 +112,11 @@ public class ClientSessionEntity extends SessionEntity {
|
|||
this.notes = notes;
|
||||
}
|
||||
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
|
||||
public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
|
||||
return authenticatorStatus;
|
||||
}
|
||||
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
|
||||
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
|
||||
this.authenticatorStatus = authenticatorStatus;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ClientUserSessionNoteEntity> 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<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
return null;
|
||||
public Map<String, ExecutionStatus> getExecutionStatus() {
|
||||
Map<String, ExecutionStatus> 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<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
|
||||
public UserModel getAuthenticatedUser() {
|
||||
return null;
|
||||
return session.users().getUserById(entity.getUserId(), realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticatedUser(UserModel user) {
|
||||
|
||||
entity.setUserId(user.getId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -155,19 +155,24 @@ public class ClientSessionAdapter implements ClientSessionModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
public Map<String, ExecutionStatus> 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<String, UserSessionModel.AuthenticatorStatus> status) {
|
||||
entity.setAuthenticatorStatus(status);
|
||||
public void clearExecutionStatus() {
|
||||
entity.getAuthenticatorStatus().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUserSessionNotes() {
|
||||
entity.getUserSessionNotes().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
|
||||
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
|
||||
private String authUserId;
|
||||
|
||||
private UserSessionEntity session;
|
||||
|
@ -122,11 +121,11 @@ public class ClientSessionEntity {
|
|||
this.authUserId = authUserId;
|
||||
}
|
||||
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
|
||||
public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
|
||||
return authenticatorStatus;
|
||||
}
|
||||
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
|
||||
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
|
||||
this.authenticatorStatus = authenticatorStatus;
|
||||
}
|
||||
|
||||
|
|
|
@ -171,22 +171,26 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
|
||||
public Map<String, ExecutionStatus> 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<String, UserSessionModel.AuthenticatorStatus> status) {
|
||||
entity.setAuthenticatorStatus(status);
|
||||
public void clearExecutionStatus() {
|
||||
entity.getAuthenticatorStatus().clear();
|
||||
updateMongoEntity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUserSessionNotes() {
|
||||
entity.getUserSessionNotes().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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<String> protocolMappers;
|
||||
private Map<String, String> notes = 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;
|
||||
|
||||
public String getId() {
|
||||
|
@ -130,11 +129,11 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
|
|||
this.sessionId = sessionId;
|
||||
}
|
||||
|
||||
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
|
||||
public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
|
||||
return authenticatorStatus;
|
||||
}
|
||||
|
||||
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
|
||||
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
|
||||
this.authenticatorStatus = authenticatorStatus;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -21,17 +22,19 @@ 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.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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;
|
||||
|
@ -323,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();
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
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) {
|
||||
|
@ -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 {
|
||||
checkClientSession();
|
||||
|
@ -428,7 +450,53 @@ 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);
|
||||
}
|
||||
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);
|
||||
|
@ -444,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 {
|
||||
|
@ -458,7 +527,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 +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) {
|
||||
if (authenticatedUser != null) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -20,5 +20,7 @@ public interface Authenticator extends Provider {
|
|||
*/
|
||||
void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user);
|
||||
|
||||
void action(AuthenticatorContext context);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<String, String> inputData = context.getHttpRequest().getDecodedFormParameters();
|
||||
List<UserCredentialModel> 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() {
|
||||
|
||||
|
|
|
@ -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<String, Object>());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
|
||||
|
|
|
@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
|
@ -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<String, String> 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<String, String> 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<String, String> 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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue