Merge pull request #697 from mposolda/master

Cookie & rememberMe fix. Fixing testsuite with mongo
This commit is contained in:
Marek Posolda 2014-09-16 22:15:27 +02:00
commit 364174fb7e
10 changed files with 105 additions and 14 deletions

View file

@ -40,7 +40,11 @@
<#if realm.rememberMe> <#if realm.rememberMe>
<div class="checkbox"> <div class="checkbox">
<label> <label>
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me <#if login.rememberMe??>
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> Remember Me
<#else>
<input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
</#if>
</label> </label>
</div> </div>
</#if> </#if>

View file

@ -34,11 +34,14 @@ public class LoginBean {
private String passwordToken; private String passwordToken;
private String rememberMe;
public LoginBean(MultivaluedMap<String, String> formData){ public LoginBean(MultivaluedMap<String, String> formData){
if (formData != null) { if (formData != null) {
username = formData.getFirst("username"); username = formData.getFirst("username");
password = formData.getFirst("password"); password = formData.getFirst("password");
passwordToken = formData.getFirst("password-token"); passwordToken = formData.getFirst("password-token");
rememberMe = formData.getFirst("rememberMe");
} }
} }
@ -53,4 +56,8 @@ public class LoginBean {
public String getPasswordToken() { public String getPasswordToken() {
return passwordToken; return passwordToken;
} }
public String getRememberMe() {
return rememberMe;
}
} }

View file

@ -70,6 +70,9 @@ public class MongoUserSessionProvider implements UserSessionProvider {
MongoUserSessionEntity userSessionEntity = entities.get(0); MongoUserSessionEntity userSessionEntity = entities.get(0);
List<MongoClientSessionEntity> sessions = userSessionEntity.getClientSessions(); List<MongoClientSessionEntity> sessions = userSessionEntity.getClientSessions();
if (sessions == null) {
return null;
}
for (MongoClientSessionEntity s : sessions) { for (MongoClientSessionEntity s : sessions) {
if (s.getId().equals(id)) { if (s.getId().equals(id)) {
return new ClientSessionAdapter(session, this, realm, s, userSessionEntity, invocationContext); return new ClientSessionAdapter(session, this, realm, s, userSessionEntity, invocationContext);
@ -240,6 +243,10 @@ public class MongoUserSessionProvider implements UserSessionProvider {
.get(); .get();
List<MongoUserSessionEntity> userSessionEntities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext); List<MongoUserSessionEntity> userSessionEntities = mongoStore.loadEntities(MongoUserSessionEntity.class, query, invocationContext);
for (MongoUserSessionEntity e : userSessionEntities) { for (MongoUserSessionEntity e : userSessionEntities) {
if (e.getClientSessions() == null) {
continue;
}
List<MongoClientSessionEntity> remove = new LinkedList<MongoClientSessionEntity>(); List<MongoClientSessionEntity> remove = new LinkedList<MongoClientSessionEntity>();
for (MongoClientSessionEntity c : e.getClientSessions()) { for (MongoClientSessionEntity c : e.getClientSessions()) {
if (c.getClientId().equals(client.getId())) { if (c.getClientId().equals(client.getId())) {

View file

@ -131,6 +131,10 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
@Override @Override
public List<ClientSessionModel> getClientSessions() { public List<ClientSessionModel> getClientSessions() {
List<ClientSessionModel> sessions = new LinkedList<ClientSessionModel>(); List<ClientSessionModel> sessions = new LinkedList<ClientSessionModel>();
if (entity.getClientSessions() == null) {
return sessions;
}
for (MongoClientSessionEntity e : entity.getClientSessions()) { for (MongoClientSessionEntity e : entity.getClientSessions()) {
sessions.add(new ClientSessionAdapter(keycloakSession, provider, realm, e, entity, invocationContext)); sessions.add(new ClientSessionAdapter(keycloakSession, provider, realm, e, entity, invocationContext));
} }

View file

@ -23,7 +23,7 @@ public class AppAuthManager extends AuthenticationManager {
if (authResult == null) return null; if (authResult == null) return null;
// refresh the cookies! // refresh the cookies!
createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, connection); createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, connection);
if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, uriInfo, connection); if (authResult.getSession().isRememberMe()) createRememberMeCookie(realm, authResult.getUser().getUsername(), uriInfo, connection);
return authResult; return authResult;
} }

View file

@ -87,8 +87,8 @@ public class AuthenticationManager {
if (session != null) { if (session != null) {
token.setSessionState(session.getId()); token.setSessionState(session.getId());
} }
if (realm.getSsoSessionIdleTimeout() > 0) { if (realm.getSsoSessionMaxLifespan() > 0) {
token.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout()); token.expiration(Time.currentTime() + realm.getSsoSessionMaxLifespan());
} }
return token; return token;
} }
@ -100,7 +100,7 @@ public class AuthenticationManager {
boolean secureOnly = realm.getSslRequired().isRequired(connection); boolean secureOnly = realm.getSslRequired().isRequired(connection);
int maxAge = NewCookie.DEFAULT_MAX_AGE; int maxAge = NewCookie.DEFAULT_MAX_AGE;
if (session.isRememberMe()) { if (session.isRememberMe()) {
maxAge = realm.getSsoSessionIdleTimeout(); maxAge = realm.getSsoSessionMaxLifespan();
} }
logger.debugv("Create login cookie - name: {0}, path: {1}, max-age: {2}", KEYCLOAK_IDENTITY_COOKIE, cookiePath, maxAge); logger.debugv("Create login cookie - name: {0}, path: {1}, max-age: {2}", KEYCLOAK_IDENTITY_COOKIE, cookiePath, maxAge);
CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true); CookieHelper.addCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, cookiePath, null, null, maxAge, secureOnly, true);
@ -116,12 +116,12 @@ public class AuthenticationManager {
} }
public void createRememberMeCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) { public void createRememberMeCookie(RealmModel realm, String username, UriInfo uriInfo, ClientConnection connection) {
String path = getIdentityCookiePath(realm, uriInfo); String path = getIdentityCookiePath(realm, uriInfo);
boolean secureOnly = realm.getSslRequired().isRequired(connection); boolean secureOnly = realm.getSslRequired().isRequired(connection);
// remember me cookie should be persistent // remember me cookie should be persistent (hardcoded to 1 month for now)
//NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true); //NewCookie cookie = new NewCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getCentralLoginLifespan(), secureOnly);// todo httponly , true);
CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, "true", path, null, null, realm.getSsoSessionIdleTimeout(), secureOnly, true); CookieHelper.addCookie(KEYCLOAK_REMEMBER_ME, username, path, null, null, 2592000, secureOnly, true);
} }
protected String encodeToken(RealmModel realm, Object token) { protected String encodeToken(RealmModel realm, Object token) {
@ -136,7 +136,6 @@ public class AuthenticationManager {
String path = getIdentityCookiePath(realm, uriInfo); String path = getIdentityCookiePath(realm, uriInfo);
expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection); expireCookie(realm, KEYCLOAK_IDENTITY_COOKIE, path, true, connection);
expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection); expireCookie(realm, KEYCLOAK_SESSION_COOKIE, path, false, connection);
expireRememberMeCookie(realm, uriInfo, connection);
} }
public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) { public static void expireRememberMeCookie(RealmModel realm, UriInfo uriInfo, ClientConnection connection) {
logger.debug("Expiring remember me cookie"); logger.debug("Expiring remember me cookie");

View file

@ -59,6 +59,7 @@ import javax.ws.rs.Path;
import javax.ws.rs.Produces; import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam; import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
@ -523,7 +524,7 @@ public class TokenService {
AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData); AuthenticationStatus status = authManager.authenticateForm(session, clientConnection, realm, formData);
if (remember) { if (remember) {
authManager.createRememberMeCookie(realm, uriInfo, clientConnection); authManager.createRememberMeCookie(realm, username, uriInfo, clientConnection);
} else { } else {
authManager.expireRememberMeCookie(realm, uriInfo, clientConnection); authManager.expireRememberMeCookie(realm, uriInfo, clientConnection);
} }
@ -962,9 +963,21 @@ public class TokenService {
LoginFormsProvider forms = Flows.forms(session, realm, client, uriInfo); LoginFormsProvider forms = Flows.forms(session, realm, client, uriInfo);
if (loginHint != null) { String rememberMeUsername = null;
Cookie rememberMeCookie = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
if (rememberMeCookie != null && !"".equals(rememberMeCookie.getValue())) {
rememberMeUsername = rememberMeCookie.getValue();
}
if (loginHint != null || rememberMeUsername != null) {
MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>(); MultivaluedMap<String, String> formData = new MultivaluedMapImpl<String, String>();
formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
if (loginHint != null) {
formData.add(AuthenticationManager.FORM_USERNAME, loginHint);
} else {
formData.add(AuthenticationManager.FORM_USERNAME, rememberMeUsername);
formData.add("rememberMe", "on");
}
forms.setFormData(formData); forms.setFormData(formData);
} }

View file

@ -92,7 +92,6 @@ public class OAuthFlows {
if (state != null) if (state != null)
redirectUri.queryParam(OAuth2Constants.STATE, state); redirectUri.queryParam(OAuth2Constants.STATE, state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build()); Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
Cookie remember = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE); Cookie sessionCookie = request.getHttpHeaders().getCookies().get(AuthenticationManager.KEYCLOAK_SESSION_COOKIE);
if (sessionCookie != null) { if (sessionCookie != null) {
@ -112,7 +111,7 @@ public class OAuthFlows {
// refresh the cookies! // refresh the cookies!
authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, clientConnection); authManager.createLoginCookie(realm, accessCode.getUser(), userSession, uriInfo, clientConnection);
if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, uriInfo, clientConnection); if (userSession.isRememberMe()) authManager.createRememberMeCookie(realm, accessCode.getUser().getUsername(), uriInfo, clientConnection);
return location.build(); return location.build();
} }

View file

@ -27,7 +27,9 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.keycloak.OAuth2Constants; import org.keycloak.OAuth2Constants;
import org.keycloak.events.Details; import org.keycloak.events.Details;
import org.keycloak.events.Event;
import org.keycloak.models.BrowserSecurityHeaders; import org.keycloak.models.BrowserSecurityHeaders;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
@ -165,6 +167,49 @@ public class LoginTest {
events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent(); events.expectLogin().user(userId).detail(Details.USERNAME, "login@test.com").assertEvent();
} }
@Test
public void loginWithRememberMe() {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setRememberMe(true);
}
});
try {
loginPage.open();
Assert.assertFalse(loginPage.isRememberMeChecked());
loginPage.setRememberMe(true);
Assert.assertTrue(loginPage.isRememberMeChecked());
loginPage.login("login-test", "password");
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
Event loginEvent = events.expectLogin().user(userId)
.detail(Details.USERNAME, "login-test")
.detail(Details.REMEMBER_ME, "true")
.assertEvent();
String sessionId = loginEvent.getSessionId();
// Expire session
keycloakRule.removeUserSession(sessionId);
// Assert rememberMe checked and username/email prefilled
loginPage.open();
Assert.assertTrue(loginPage.isRememberMeChecked());
Assert.assertEquals("login-test", loginPage.getUsername());
loginPage.setRememberMe(false);
} finally {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.setRememberMe(false);
}
});
}
}
@Test @Test
public void loginCancel() { public void loginCancel() {
loginPage.open(); loginPage.open();

View file

@ -44,6 +44,9 @@ public class LoginPage extends AbstractPage {
@FindBy(id = "totp") @FindBy(id = "totp")
private WebElement totp; private WebElement totp;
@FindBy(id = "rememberMe")
private WebElement rememberMe;
@FindBy(name = "login") @FindBy(name = "login")
private WebElement submitButton; private WebElement submitButton;
@ -119,6 +122,16 @@ public class LoginPage extends AbstractPage {
recoverUsernameLink.click(); recoverUsernameLink.click();
} }
public void setRememberMe(boolean enable) {
boolean current = rememberMe.isSelected();
if (current != enable) {
rememberMe.click();
}
}
public boolean isRememberMeChecked() {
return rememberMe.isSelected();
}
@Override @Override
public void open() { public void open() {