more
This commit is contained in:
parent
c12fe28b2d
commit
b54061fc3f
10 changed files with 120 additions and 32 deletions
|
@ -38,6 +38,7 @@ public class AuthenticationProcessor {
|
|||
protected EventBuilder event;
|
||||
protected HttpRequest request;
|
||||
protected String flowId;
|
||||
protected boolean userSessionCreated;
|
||||
|
||||
|
||||
public static enum Status {
|
||||
|
@ -79,6 +80,14 @@ public class AuthenticationProcessor {
|
|||
return session;
|
||||
}
|
||||
|
||||
public UserSessionModel getUserSession() {
|
||||
return userSession;
|
||||
}
|
||||
|
||||
public boolean isUserSessionCreated() {
|
||||
return userSessionCreated;
|
||||
}
|
||||
|
||||
public AuthenticationProcessor setRealm(RealmModel realm) {
|
||||
this.realm = realm;
|
||||
return this;
|
||||
|
@ -339,6 +348,40 @@ public class AuthenticationProcessor {
|
|||
throw new AuthException(Error.UNKNOWN_USER);
|
||||
}
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -43,15 +43,28 @@ public class AbstractFormAuthenticator {
|
|||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
|
@ -62,6 +75,7 @@ public class AbstractFormAuthenticator {
|
|||
return true;
|
||||
}
|
||||
if (!user.isEnabled()) {
|
||||
context.getEvent().user(user);
|
||||
context.getEvent().error(Errors.USER_DISABLED);
|
||||
Response challengeResponse = disabledUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse);
|
||||
|
@ -69,6 +83,7 @@ public class AbstractFormAuthenticator {
|
|||
}
|
||||
if (context.getRealm().isBruteForceProtected()) {
|
||||
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
|
||||
context.getEvent().user(user);
|
||||
context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED);
|
||||
Response challengeResponse = temporarilyDisabledUser(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse);
|
||||
|
|
|
@ -35,26 +35,25 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
|
|||
validatePassword(context);
|
||||
}
|
||||
|
||||
protected Response badPassword(AuthenticatorContext context) {
|
||||
return loginForm(context).setError(Messages.INVALID_USER).createLogin();
|
||||
}
|
||||
|
||||
|
||||
public void validatePassword(AuthenticatorContext context) {
|
||||
MultivaluedMap<String, String> inputData = context.getHttpRequest().getFormParameters();
|
||||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.PASSWORD);
|
||||
if (password == null) {
|
||||
if (context.getUser() != null) {
|
||||
context.getEvent().user(context.getUser());
|
||||
}
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = badPassword(context);
|
||||
Response challengeResponse = invalidCredentials(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.password(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
context.getEvent().user(context.getUser());
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = badPassword(context);
|
||||
Response challengeResponse = invalidCredentials(context);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resources.LoginActionsService;
|
||||
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
|
@ -34,7 +35,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
|
|||
@Override
|
||||
public void authenticate(AuthenticatorContext context) {
|
||||
if (!isActionUrl(context)) {
|
||||
Response challengeResponse = challenge(context);
|
||||
Response challengeResponse = challenge(context, null);
|
||||
context.challenge(challengeResponse);
|
||||
return;
|
||||
}
|
||||
|
@ -46,34 +47,34 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A
|
|||
List<UserCredentialModel> credentials = new LinkedList<>();
|
||||
String password = inputData.getFirst(CredentialRepresentation.TOTP);
|
||||
if (password == null) {
|
||||
Response challengeResponse = challenge(context);
|
||||
Response challengeResponse = challenge(context, null);
|
||||
context.challenge(challengeResponse);
|
||||
return;
|
||||
}
|
||||
credentials.add(UserCredentialModel.totp(password));
|
||||
boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials);
|
||||
if (!valid) {
|
||||
context.getEvent().error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = challenge(context);
|
||||
context.getEvent().user(context.getUser())
|
||||
.error(Errors.INVALID_USER_CREDENTIALS);
|
||||
Response challengeResponse = challenge(context, Messages.INVALID_TOTP);
|
||||
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
|
||||
return;
|
||||
}
|
||||
context.success();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean requiresUser() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Response challenge(AuthenticatorContext context) {
|
||||
protected Response challenge(AuthenticatorContext context, String error) {
|
||||
ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession());
|
||||
URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode);
|
||||
LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
|
||||
.setActionUri(action)
|
||||
.setClientSessionCode(clientSessionCode.getCode());
|
||||
|
||||
if (error != null) forms.setError(error);
|
||||
|
||||
return forms.createLoginTotp();
|
||||
}
|
||||
|
|
|
@ -277,7 +277,21 @@ public class AuthorizationEndpoint {
|
|||
.setUriInfo(uriInfo)
|
||||
.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) {
|
||||
|
|
|
@ -425,6 +425,17 @@ public class AuthenticationManager {
|
|||
public static Response nextActionAfterAuthentication(KeycloakSession session, UserSessionModel userSession, ClientSessionModel clientSession,
|
||||
ClientConnection clientConnection,
|
||||
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();
|
||||
UserModel user = userSession.getUser();
|
||||
isForcePasswordUpdateRequired(realm, user);
|
||||
|
@ -502,12 +513,12 @@ public class AuthenticationManager {
|
|||
.createOAuthGrant(clientSession);
|
||||
}
|
||||
}
|
||||
|
||||
event.success();
|
||||
return redirectAfterSuccessfulFlow(session, realm , userSession, clientSession, request, uriInfo, clientConnection);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void isForcePasswordUpdateRequired(RealmModel realm, UserModel user) {
|
||||
int daysToExpirePassword = realm.getPasswordPolicy().getDaysToExpirePassword();
|
||||
if(daysToExpirePassword != -1) {
|
||||
|
|
5
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java
Normal file → Executable file
5
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/KerberosLdapTest.java
Normal file → Executable file
|
@ -18,11 +18,13 @@ import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.testsuite.AssertEvents;
|
||||
import org.keycloak.testsuite.rule.KerberosRule;
|
||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||
import org.keycloak.testsuite.rule.WebRule;
|
||||
import org.keycloak.testsuite.utils.CredentialHelper;
|
||||
|
||||
/**
|
||||
* Test of LDAPFederationProvider (Kerberos backed by LDAP)
|
||||
|
@ -41,6 +43,7 @@ public class KerberosLdapTest extends AbstractKerberosTest {
|
|||
|
||||
@Override
|
||||
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||
CredentialHelper.setRequiredCredential(CredentialRepresentation.KERBEROS, appRealm);
|
||||
URL url = getClass().getResource("/kerberos-test/kerberos-app-keycloak.json");
|
||||
keycloakRule.deployApplication("kerberos-portal", "/kerberos-portal", KerberosCredDelegServlet.class, url.getPath(), "user");
|
||||
|
||||
|
@ -126,7 +129,7 @@ public class KerberosLdapTest extends AbstractKerberosTest {
|
|||
.client("kerberos-app")
|
||||
.user(keycloakRule.getUser("test", "jduke").getId())
|
||||
.detail(Details.REDIRECT_URI, KERBEROS_APP_URL)
|
||||
.detail(Details.AUTH_METHOD, "spnego")
|
||||
//.detail(Details.AUTH_METHOD, "spnego")
|
||||
.detail(Details.USERNAME, "jduke")
|
||||
.assertEvent();
|
||||
|
||||
|
|
|
@ -145,9 +145,9 @@ public class LoginTest {
|
|||
|
||||
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 {
|
||||
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||
@Override
|
||||
|
@ -225,7 +225,7 @@ public class LoginTest {
|
|||
driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none");
|
||||
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
|
||||
|
@ -359,7 +359,7 @@ public class LoginTest {
|
|||
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
|
||||
events.expectLogin().user(userId).assertEvent();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -110,9 +110,11 @@ public class LoginTotpTest {
|
|||
loginTotpPage.assertCurrent();
|
||||
|
||||
loginTotpPage.login("123456");
|
||||
loginTotpPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid authenticator code.", loginPage.getError());
|
||||
|
||||
loginPage.assertCurrent();
|
||||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
//loginPage.assertCurrent(); // Invalid authenticator code.
|
||||
//Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
|
||||
events.expectLogin().error("invalid_user_credentials").session((String) null).assertEvent();
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ public class SSOTest {
|
|||
|
||||
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);
|
||||
|
||||
|
@ -139,7 +139,7 @@ public class SSOTest {
|
|||
|
||||
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.assertNotNull(oauth2.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||
|
||||
|
|
Loading…
Reference in a new issue