Skip to content

Commit

Permalink
KEYCLOAK-8125
Browse files Browse the repository at this point in the history
  • Loading branch information
martin-kanis authored and stianst committed Nov 12, 2018
1 parent e132a7f commit ddbef68
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.keycloak.credential.hash.PasswordHashProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.UserCredentialModel;
Expand Down Expand Up @@ -56,25 +57,19 @@ public void action(AuthenticationFlowContext context) {

}

protected Response invalidUser(AuthenticationFlowContext context) {
return context.form()
.setError(Messages.INVALID_USER)
.createLogin();
}
protected Response challenge(AuthenticationFlowContext context, String error) {
LoginFormsProvider form = context.form();
if (error != null) form.setError(error);

protected Response disabledUser(AuthenticationFlowContext context) {
return context.form()
.setError(Messages.ACCOUNT_DISABLED).createLogin();
return createLoginForm(form);
}

protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
return context.form()
.setError(Messages.INVALID_USER).createLogin();
protected Response createLoginForm(LoginFormsProvider form) {
return form.createLogin();
}

protected Response invalidCredentials(AuthenticationFlowContext context) {
return context.form()
.setError(Messages.INVALID_USER).createLogin();
protected String tempDisabledError() {
return Messages.INVALID_USER;
}

protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) {
Expand Down Expand Up @@ -112,7 +107,7 @@ public boolean invalidUser(AuthenticationFlowContext context, UserModel user) {
if (user == null) {
dummyHash(context);
context.getEvent().error(Errors.USER_NOT_FOUND);
Response challengeResponse = invalidUser(context);
Response challengeResponse = challenge(context, Messages.INVALID_USER);
context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse);
return true;
}
Expand All @@ -123,7 +118,7 @@ public boolean enabledUser(AuthenticationFlowContext context, UserModel user) {
if (!user.isEnabled()) {
context.getEvent().user(user);
context.getEvent().error(Errors.USER_DISABLED);
Response challengeResponse = disabledUser(context);
Response challengeResponse = challenge(context, Messages.ACCOUNT_DISABLED);
// this is not a failure so don't call failureChallenge.
//context.failureChallenge(AuthenticationFlowError.USER_DISABLED, challengeResponse);
context.forceChallenge(challengeResponse);
Expand All @@ -137,7 +132,7 @@ public boolean validateUserAndPassword(AuthenticationFlowContext context, Multiv
String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME);
if (username == null) {
context.getEvent().error(Errors.USER_NOT_FOUND);
Response challengeResponse = invalidUser(context);
Response challengeResponse = challenge(context, Messages.INVALID_USER);
context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse);
return false;
}
Expand Down Expand Up @@ -200,19 +195,19 @@ public boolean validatePassword(AuthenticationFlowContext context, UserModel use
} else {
context.getEvent().user(user);
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = invalidCredentials(context);
Response challengeResponse = challenge(context, Messages.INVALID_USER);
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
context.clearUser();
return false;
}
}

private boolean isTemporarilyDisabledByBruteForce(AuthenticationFlowContext context, UserModel user) {
protected boolean isTemporarilyDisabledByBruteForce(AuthenticationFlowContext context, UserModel user) {
if (context.getRealm().isBruteForceProtected()) {
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user)) {
context.getEvent().user(user);
context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED);
Response challengeResponse = temporarilyDisabledUser(context);
Response challengeResponse = challenge(context, tempDisabledError());
// this is not a failure so don't call failureChallenge.
//context.failureChallenge(AuthenticationFlowError.USER_TEMPORARILY_DISABLED, challengeResponse);
context.forceChallenge(challengeResponse);
Expand All @@ -221,5 +216,4 @@ private boolean isTemporarilyDisabledByBruteForce(AuthenticationFlowContext cont
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,23 @@ public void validateOTP(AuthenticationFlowContext context) {
context.resetFlow();
return;
}

UserModel userModel = context.getUser();
if (!enabledUser(context, userModel)) {
// error in context is set in enabledUser/isTemporarilyDisabledByBruteForce
return;
}

String password = inputData.getFirst(CredentialRepresentation.TOTP);
if (password == null) {
Response challengeResponse = challenge(context, null);
context.challenge(challengeResponse);
return;
}
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(),
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), userModel,
UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), password));
if (!valid) {
context.getEvent().user(context.getUser())
context.getEvent().user(userModel)
.error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = challenge(context, Messages.INVALID_TOTP);
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
Expand All @@ -77,11 +84,14 @@ public boolean requiresUser() {
return true;
}

protected Response challenge(AuthenticationFlowContext context, String error) {
LoginFormsProvider forms = context.form();
if (error != null) forms.setError(error);
@Override
protected String tempDisabledError() {
return Messages.INVALID_TOTP;
}

return forms.createLoginTotp();
@Override
protected Response createLoginForm(LoginFormsProvider form) {
return form.createLoginTotp();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,21 @@ public void authenticate(AuthenticationFlowContext context) {
String authorizationHeader = getAuthorizationHeader(context);

if (authorizationHeader == null) {
context.challenge(challengeResponse(context));
context.challenge(challenge(context, null));
return;
}

String[] challenge = getChallenge(authorizationHeader);

if (challenge == null) {
context.challenge(challengeResponse(context));
context.challenge(challenge(context, null));
return;
}

if (onAuthenticate(context, challenge)) {
context.success();
return;
}

context.setUser(null);
context.challenge(challengeResponse(context));
}

protected boolean onAuthenticate(AuthenticationFlowContext context, String[] challenge) {
Expand Down Expand Up @@ -105,28 +102,13 @@ protected String[] getChallenge(String authorizationHeader) {
}

@Override
protected Response invalidUser(AuthenticationFlowContext context) {
return challengeResponse(context);
}

@Override
protected Response disabledUser(AuthenticationFlowContext context) {
return challengeResponse(context);
}

@Override
protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
return challengeResponse(context);
}

@Override
protected Response invalidCredentials(AuthenticationFlowContext context) {
return challengeResponse(context);
protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) {
return challenge(context, null);
}

@Override
protected Response setDuplicateUserChallenge(AuthenticationFlowContext context, String eventError, String loginFormError, AuthenticationFlowError authenticatorError) {
return challengeResponse(context);
protected Response challenge(AuthenticationFlowContext context, String error) {
return Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, getHeader(context)).build();
}

@Override
Expand All @@ -148,10 +130,6 @@ public void close() {

}

private Response challengeResponse(AuthenticationFlowContext context) {
return Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, getHeader(context)).build();
}

private String getHeader(AuthenticationFlowContext context) {
return "Basic realm=\"" + context.getRealm().getName() + "\"";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,19 @@
package org.keycloak.authentication.authenticators.challenge;

import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.AuthenticationFlowError;
import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OTPPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.messages.Messages;

import javax.ws.rs.core.Response;

/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
Expand All @@ -44,7 +51,7 @@ protected boolean onAuthenticate(AuthenticationFlowContext context, String[] cha
password = password.substring(0, password.length() - otpLength);

if (checkUsernameAndPassword(context, username, password)) {
String otp = password.substring(password.length() - otpLength);
String otp = challenge[1].substring(password.length(), challenge[1].length());

if (checkOtp(context, otp)) {
return true;
Expand All @@ -55,8 +62,17 @@ protected boolean onAuthenticate(AuthenticationFlowContext context, String[] cha
}

private boolean checkOtp(AuthenticationFlowContext context, String otp) {
return context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(),
boolean valid = context.getSession().userCredentialManager().isValid(context.getRealm(), context.getUser(),
UserCredentialModel.otp(context.getRealm().getOTPPolicy().getType(), otp));

if (!valid) {
context.getEvent().user(context.getUser()).error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = challenge(context, Messages.INVALID_TOTP);
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse);
return false;
}

return true;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,45 +66,12 @@ private URI getCallbackUrl(AuthenticationFlowContext context) {
}

@Override
protected Response invalidUser(AuthenticationFlowContext context) {
protected Response challenge(AuthenticationFlowContext context, String error) {
String header = getHeader(context);
Response response = Response.status(401)
.type(MediaType.TEXT_PLAIN_TYPE)
.header(HttpHeaders.WWW_AUTHENTICATE, header)
.entity("\n" + context.form().getMessage(Messages.INVALID_USER) + "\n")
.build();
return response;
}

@Override
protected Response disabledUser(AuthenticationFlowContext context) {
String header = getHeader(context);
Response response = Response.status(401)
.type(MediaType.TEXT_PLAIN_TYPE)
.header(HttpHeaders.WWW_AUTHENTICATE, header)
.entity("\n" + context.form().getMessage(Messages.ACCOUNT_DISABLED) + "\n")
.build();
return response;
}

@Override
protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
String header = getHeader(context);
Response response = Response.status(401)
.type(MediaType.TEXT_PLAIN_TYPE)
.header(HttpHeaders.WWW_AUTHENTICATE, header)
.entity("\n" + context.form().getMessage(Messages.INVALID_USER) + "\n")
.build();
return response;
}

@Override
protected Response invalidCredentials(AuthenticationFlowContext context) {
String header = getHeader(context);
Response response = Response.status(401)
.type(MediaType.TEXT_PLAIN_TYPE)
.header(HttpHeaders.WWW_AUTHENTICATE, header)
.entity("\n" + context.form().getMessage(Messages.INVALID_USER) + "\n")
.entity("\n" + context.form().getMessage(error) + "\n")
.build();
return response;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.messages.Messages;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
Expand Down Expand Up @@ -61,27 +60,8 @@ public void authenticate(AuthenticationFlowContext context) {
}

@Override
protected Response invalidUser(AuthenticationFlowContext context) {
Response response = challenge(context).message(Messages.INVALID_USER);
return response;
}

@Override
protected Response disabledUser(AuthenticationFlowContext context) {
Response response = challenge(context).message(Messages.ACCOUNT_DISABLED);
return response;
}

@Override
protected Response temporarilyDisabledUser(AuthenticationFlowContext context) {
Response response = challenge(context).message(Messages.INVALID_USER);
return response;
}

@Override
protected Response invalidCredentials(AuthenticationFlowContext context) {
Response response = challenge(context).message(Messages.INVALID_USER);
return response;
protected Response challenge(AuthenticationFlowContext context, String error) {
return challenge(context).message(error);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,11 @@ protected void nullUserAction(final AuthenticationFlowContext context, final Rea
}

@Override
protected void userDisabledAction(AuthenticationFlowContext context, RealmModel realm, UserModel user) {
protected void userDisabledAction(AuthenticationFlowContext context, RealmModel realm, UserModel user, String eventError) {
context.getEvent().user(user);
context.getEvent().error(Errors.USER_DISABLED);

context.getEvent().error(eventError);
final DockerError error = new DockerError("UNAUTHORIZED","Invalid username or password.",
Collections.singletonList(new DockerAccess(context.getAuthenticationSession().getClientNote(DockerAuthV2Protocol.SCOPE_PARAM))));

context.failure(AuthenticationFlowError.USER_DISABLED, new ResponseBuilderImpl()
.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
Expand Down
Loading

0 comments on commit ddbef68

Please sign in to comment.