This commit is contained in:
Bill Burke 2015-06-03 18:51:57 -04:00
parent c12fe28b2d
commit b54061fc3f
10 changed files with 120 additions and 32 deletions

View file

@ -38,6 +38,7 @@ public class AuthenticationProcessor {
protected EventBuilder event; protected EventBuilder event;
protected HttpRequest request; protected HttpRequest request;
protected String flowId; protected String flowId;
protected boolean userSessionCreated;
public static enum Status { public static enum Status {
@ -79,6 +80,14 @@ public class AuthenticationProcessor {
return session; return session;
} }
public UserSessionModel getUserSession() {
return userSession;
}
public boolean isUserSessionCreated() {
return userSessionCreated;
}
public AuthenticationProcessor setRealm(RealmModel realm) { public AuthenticationProcessor setRealm(RealmModel realm) {
this.realm = realm; this.realm = realm;
return this; return this;
@ -339,6 +348,40 @@ public class AuthenticationProcessor {
throw new AuthException(Error.UNKNOWN_USER); throw new AuthException(Error.UNKNOWN_USER);
} }
return authenticationComplete(); return authenticationComplete();
}
public Response authenticateOnly() throws AuthException {
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);
}
UserModel authUser = clientSession.getAuthenticatedUser();
validateUser(authUser);
Response challenge = processFlow(flowId);
if (challenge != null) return challenge;
String username = clientSession.getAuthenticatedUser().getUsername();
if (userSession == null) { // if no authenticator attached a usersession
userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), "form", false, null, null);
userSession.setState(UserSessionModel.State.LOGGING_IN);
userSessionCreated = true;
}
TokenManager.attachClientSession(userSession, clientSession);
event.user(userSession.getUser())
.detail(Details.USERNAME, username)
.session(userSession);
return AuthenticationManager.actionRequired(session, userSession, clientSession, connection, request, uriInfo, event);
}
public Response finishAuthentication() {
event.success();
RealmModel realm = clientSession.getRealm();
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, connection);
} }

View file

@ -43,15 +43,28 @@ public class AbstractFormAuthenticator {
} }
protected Response invalidUser(AuthenticatorContext context) { protected Response invalidUser(AuthenticatorContext context) {
return loginForm(context).setError(Messages.INVALID_USER).createLogin(); return loginForm(context)
.setError(Messages.INVALID_USER)
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
.createLogin();
} }
protected Response disabledUser(AuthenticatorContext context) { protected Response disabledUser(AuthenticatorContext context) {
return loginForm(context).setError(Messages.ACCOUNT_DISABLED).createLogin(); return loginForm(context)
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
.setError(Messages.ACCOUNT_DISABLED).createLogin();
} }
protected Response temporarilyDisabledUser(AuthenticatorContext context) { protected Response temporarilyDisabledUser(AuthenticatorContext context) {
return loginForm(context).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin(); return loginForm(context)
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
.setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).createLogin();
}
protected Response invalidCredentials(AuthenticatorContext context) {
return loginForm(context)
.setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode())
.setError(Messages.INVALID_USER).createLogin();
} }
public boolean invalidUser(AuthenticatorContext context, UserModel user) { public boolean invalidUser(AuthenticatorContext context, UserModel user) {
@ -62,6 +75,7 @@ public class AbstractFormAuthenticator {
return true; return true;
} }
if (!user.isEnabled()) { if (!user.isEnabled()) {
context.getEvent().user(user);
context.getEvent().error(Errors.USER_DISABLED); context.getEvent().error(Errors.USER_DISABLED);
Response challengeResponse = disabledUser(context); Response challengeResponse = disabledUser(context);
context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse); context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse);
@ -69,6 +83,7 @@ public class AbstractFormAuthenticator {
} }
if (context.getRealm().isBruteForceProtected()) { if (context.getRealm().isBruteForceProtected()) {
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) { if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
context.getEvent().user(user);
context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED); context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED);
Response challengeResponse = temporarilyDisabledUser(context); Response challengeResponse = temporarilyDisabledUser(context);
context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse); context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse);

View file

@ -35,26 +35,25 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
validatePassword(context); validatePassword(context);
} }
protected Response badPassword(AuthenticatorContext context) {
return loginForm(context).setError(Messages.INVALID_USER).createLogin();
}
public void validatePassword(AuthenticatorContext context) { public void validatePassword(AuthenticatorContext context) {
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters(); MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
List<UserCredentialModel> credentials = new LinkedList<>(); List<UserCredentialModel> credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.PASSWORD); String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
if (password == null) { if (password == null) {
if (context.getUser() != null) {
context.getEvent().user(context.getUser());
}
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = badPassword(context); Response challengeResponse = invalidCredentials(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
return; return;
} }
credentials.add(UserCredentialModel.password(password)); credentials.add(UserCredentialModel.password(password));
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
if (!valid) { if (!valid) {
context.getEvent().user(context.getUser());
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = badPassword(context); Response challengeResponse = invalidCredentials(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
return; return;
} }

View file

@ -12,6 +12,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.LoginActionsService;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
@ -34,7 +35,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
@Override @Override
public void authenticate(AuthenticatorContext context) { public void authenticate(AuthenticatorContext context) {
if (!isActionUrl(context)) { if (!isActionUrl(context)) {
Response challengeResponse = challenge(context); Response challengeResponse = challenge(context, null);
context.challenge(challengeResponse); context.challenge(challengeResponse);
return; return;
} }
@ -46,34 +47,34 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
List<UserCredentialModel> credentials = new LinkedList<>(); List<UserCredentialModel> credentials = new LinkedList<>();
String password = inputData.getFirst(CredentialRepresentation.TOTP); String password = inputData.getFirst(CredentialRepresentation.TOTP);
if (password == null) { if (password == null) {
Response challengeResponse = challenge(context); Response challengeResponse = challenge(context, null);
context.challenge(challengeResponse); context.challenge(challengeResponse);
return; return;
} }
credentials.add(UserCredentialModel.totp(password)); credentials.add(UserCredentialModel.totp(password));
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
if (!valid) { if (!valid) {
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); context.getEvent().user(context.getUser())
Response challengeResponse = challenge(context); .error(Errors.INVALID_USER_CREDENTIALS);
Response challengeResponse = challenge(context, Messages.INVALID_TOTP);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
return; return;
} }
context.success(); context.success();
} }
@Override @Override
public boolean requiresUser() { public boolean requiresUser() {
return true; return true;
} }
protected Response challenge(AuthenticatorContext context) { protected Response challenge(AuthenticatorContext context, String error) {
ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode); URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode);
LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class) LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
.setActionUri(action) .setActionUri(action)
.setClientSessionCode(clientSessionCode.getCode()); .setClientSessionCode(clientSessionCode.getCode());
if (error != null) forms.setError(error);
return forms.createLoginTotp(); return forms.createLoginTotp();
} }

View file

@ -277,7 +277,21 @@ public class AuthorizationEndpoint {
.setUriInfo(uriInfo) .setUriInfo(uriInfo)
.setRequest(request); .setRequest(request);
return processor.authenticate(); Response challenge = processor.authenticateOnly();
if (challenge != null && prompt != null && prompt.equals("none")) {
if (processor.isUserSessionCreated()) {
session.sessions().removeUserSession(realm, processor.getUserSession());
}
OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers, event);
return oauth.cancelLogin(clientSession);
}
if (challenge == null) {
return processor.finishAuthentication();
} else {
return challenge;
}
} }
protected Response oldBrowserAuthentication(String accessCode) { protected Response oldBrowserAuthentication(String accessCode) {

View file

@ -425,6 +425,17 @@ public class AuthenticationManager {
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession, public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
ClientConnection clientConnection, ClientConnection clientConnection,
HttpRequest request, UriInfo uriInfo, EventBuilder event) { HttpRequest request, UriInfo uriInfo, EventBuilder event) {
Response requiredAction = actionRequired(session, userSession, clientSession, clientConnection, request, uriInfo, event);
if (requiredAction != null) return requiredAction;
event.success();
RealmModel realm = clientSession.getRealm();
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
}
public static Response actionRequired(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
ClientConnection clientConnection,
HttpRequest request, UriInfo uriInfo, EventBuilder event) {
RealmModel realm = clientSession.getRealm(); RealmModel realm = clientSession.getRealm();
UserModel user = userSession.getUser(); UserModel user = userSession.getUser();
isForcePasswordUpdateRequired(realm, user); isForcePasswordUpdateRequired(realm, user);
@ -442,7 +453,7 @@ public class AuthenticationManager {
if (!requiredActions.isEmpty()) { if (!requiredActions.isEmpty()) {
Iterator<String> i = user.getRequiredActions().iterator(); Iterator<String> i = user.getRequiredActions().iterator();
String action = i.next(); String action = i.next();
if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name()) && Validation.isEmpty(user.getEmail())) { if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name()) && Validation.isEmpty(user.getEmail())) {
if (i.hasNext()) if (i.hasNext())
action = i.next(); action = i.next();
@ -502,12 +513,12 @@ public class AuthenticationManager {
.createOAuthGrant(clientSession); .createOAuthGrant(clientSession);
} }
} }
return null;
event.success();
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
} }
private static void isForcePasswordUpdateRequired(RealmModel realm, UserModel user) { private static void isForcePasswordUpdateRequired(RealmModel realm, UserModel user) {
int daysToExpirePassword = realm.getPasswordPolicy().getDaysToExpirePassword(); int daysToExpirePassword = realm.getPasswordPolicy().getDaysToExpirePassword();
if(daysToExpirePassword != -1) { if(daysToExpirePassword != -1) {

View file

@ -18,11 +18,13 @@ import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.RealmManager; import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.AssertEvents; import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.rule.KerberosRule; import org.keycloak.testsuite.rule.KerberosRule;
import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebRule; import org.keycloak.testsuite.rule.WebRule;
import org.keycloak.testsuite.utils.CredentialHelper;
/** /**
* Test of LDAPFederationProvider (Kerberos backed by LDAP) * Test of LDAPFederationProvider (Kerberos backed by LDAP)
@ -41,6 +43,7 @@ public class KerberosLdapTest extends AbstractKerberosTest {
@Override @Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
CredentialHelper.setRequiredCredential(CredentialRepresentation.KERBEROS, appRealm);
URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json"); URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
keycloakRule.deployApplication("kerberos-portal", "/kerberos-portal", KerberosCredDelegServlet.class, url.getPath(), "user"); keycloakRule.deployApplication("kerberos-portal", "/kerberos-portal", KerberosCredDelegServlet.class, url.getPath(), "user");
@ -126,7 +129,7 @@ public class KerberosLdapTest extends AbstractKerberosTest {
.client("kerberos-app") .client("kerberos-app")
.user(keycloakRule.getUser("test", "jduke").getId()) .user(keycloakRule.getUser("test", "jduke").getId())
.detail(Details.REDIRECT_URI, KERBEROS_APP_URL) .detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
.detail(Details.AUTH_METHOD, "spnego") //.detail(Details.AUTH_METHOD, "spnego")
.detail(Details.USERNAME, "jduke") .detail(Details.USERNAME, "jduke")
.assertEvent(); .assertEvent();

View file

@ -145,9 +145,9 @@ public class LoginTest {
loginPage.assertCurrent(); loginPage.assertCurrent();
Assert.assertEquals("Invalid username or password.", loginPage.getError()); Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent(); events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent();
} finally { } finally {
keycloakRule.configure(new KeycloakRule.KeycloakSetup() { keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
@Override @Override
@ -225,7 +225,7 @@ public class LoginTest {
driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none"); driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().user(userId).removeDetail(Details.USERNAME).detail(Details.AUTH_METHOD, "sso").assertEvent(); events.expectLogin().user(userId).removeDetail(Details.USERNAME).assertEvent();
} }
@Test @Test
@ -359,7 +359,7 @@ public class LoginTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent(); events.expectLogin().user(userId).assertEvent();
} }
@Test @Test

View file

@ -110,9 +110,11 @@ public class LoginTotpTest {
loginTotpPage.assertCurrent(); loginTotpPage.assertCurrent();
loginTotpPage.login("123456"); loginTotpPage.login("123456");
loginTotpPage.assertCurrent();
Assert.assertEquals("Invalid authenticator code.", loginPage.getError());
loginPage.assertCurrent(); //loginPage.assertCurrent(); // Invalid authenticator code.
Assert.assertEquals("Invalid username or password.", loginPage.getError()); //Assert.assertEquals("Invalid username or password.", loginPage.getError());
events.expectLogin().error("invalid_user_credentials").session((String) null).assertEvent(); events.expectLogin().error("invalid_user_credentials").session((String) null).assertEvent();
} }

View file

@ -92,7 +92,7 @@ public class SSOTest {
assertTrue(profilePage.isCurrent()); assertTrue(profilePage.isCurrent());
String sessionId2 = events.expectLogin().detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).client("test-app").assertEvent().getSessionId(); String sessionId2 = events.expectLogin().removeDetail(Details.USERNAME).client("test-app").assertEvent().getSessionId();
assertEquals(sessionId, sessionId2); assertEquals(sessionId, sessionId2);
@ -139,7 +139,7 @@ public class SSOTest {
oauth2.openLoginForm(); oauth2.openLoginForm();
events.expectLogin().session(login2.getSessionId()).detail(Details.AUTH_METHOD, "sso").removeDetail(Details.USERNAME).assertEvent(); events.expectLogin().session(login2.getSessionId()).removeDetail(Details.USERNAME).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, RequestType.valueOf(driver2.getTitle())); Assert.assertEquals(RequestType.AUTH_RESPONSE, RequestType.valueOf(driver2.getTitle()));
Assert.assertNotNull(oauth2.getCurrentQuery().get(OAuth2Constants.CODE)); Assert.assertNotNull(oauth2.getCurrentQuery().get(OAuth2Constants.CODE));