Cookies were not reset on administrative password change of a user account. This allowed accounts with changed passwords to continue authenticating. Cookies are now reset on password changes, they are validated on each page request, AND they will now expire 7 days after generation.tags/v1.4.0
note: "The default access restriction has been elevated from NONE to PUSH and anonymous push access has been disabled." | note: "The default access restriction has been elevated from NONE to PUSH and anonymous push access has been disabled." | ||||
html: ~ | html: ~ | ||||
text: ~ | text: ~ | ||||
security: ~ | |||||
security: | |||||
- ''issue-361: Cookies were not reset on administrative password change of a user account. | |||||
This allowed accounts with changed passwords to continue authenticating. | |||||
Cookies are now reset on password changes, they are validated on each page request, | |||||
AND they will now expire 7 days after generation. | |||||
'' | |||||
fixes: | fixes: | ||||
- Fixed incorrect tagger attribution in the dashboard (issue-276) | - Fixed incorrect tagger attribution in the dashboard (issue-276) | ||||
- Fixed support for implied SSH urls in web.otherUrls (issue-311) | - Fixed support for implied SSH urls in web.otherUrls (issue-311) |
} | } | ||||
read(); | read(); | ||||
originalUser = users.remove(username.toLowerCase()); | originalUser = users.remove(username.toLowerCase()); | ||||
if (originalUser != null) { | |||||
cookies.remove(originalUser.cookie); | |||||
} | |||||
users.put(model.username.toLowerCase(), model); | users.put(model.username.toLowerCase(), model); | ||||
// null check on "final" teams because JSON-sourced UserModel | // null check on "final" teams because JSON-sourced UserModel | ||||
// can have a null teams object | // can have a null teams object |
return false; | return false; | ||||
} | } | ||||
// change the cookie | |||||
user.cookie = StringUtils.getSHA1(user.username + password); | |||||
String type = settings.get(Keys.realm.passwordStorage).getString("md5"); | String type = settings.get(Keys.realm.passwordStorage).getString("md5"); | ||||
if (type.equalsIgnoreCase("md5")) { | if (type.equalsIgnoreCase("md5")) { | ||||
// store MD5 digest of password | // store MD5 digest of password |
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.concurrent.TimeUnit; | |||||
import javax.servlet.http.Cookie; | import javax.servlet.http.Cookie; | ||||
import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||
return null; | return null; | ||||
} | } | ||||
UserModel user = null; | |||||
// try to authenticate by cookie | // try to authenticate by cookie | ||||
UserModel user = authenticate(httpRequest.getCookies()); | |||||
if (user != null) { | |||||
flagWicketSession(AuthenticationType.COOKIE); | |||||
logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}", | |||||
String cookie = getCookie(httpRequest); | |||||
if (!StringUtils.isEmpty(cookie)) { | |||||
user = userManager.getUserModel(cookie.toCharArray()); | |||||
if (user != null) { | |||||
flagWicketSession(AuthenticationType.COOKIE); | |||||
logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}", | |||||
user.username, httpRequest.getRemoteAddr())); | user.username, httpRequest.getRemoteAddr())); | ||||
return user; | |||||
return user; | |||||
} | |||||
} | } | ||||
// try to authenticate by BASIC | // try to authenticate by BASIC | ||||
return null; | return null; | ||||
} | } | ||||
/** | |||||
* Authenticate a user based on their cookie. | |||||
* | |||||
* @param cookies | |||||
* @return a user object or null | |||||
*/ | |||||
protected UserModel authenticate(Cookie[] cookies) { | |||||
if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) { | |||||
if (cookies != null && cookies.length > 0) { | |||||
for (Cookie cookie : cookies) { | |||||
if (cookie.getName().equals(Constants.NAME)) { | |||||
String value = cookie.getValue(); | |||||
return userManager.getUserModel(value.toCharArray()); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
protected void flagWicketSession(AuthenticationType authenticationType) { | protected void flagWicketSession(AuthenticationType authenticationType) { | ||||
RequestCycle requestCycle = RequestCycle.get(); | RequestCycle requestCycle = RequestCycle.get(); | ||||
if (requestCycle != null) { | if (requestCycle != null) { | ||||
return user; | return user; | ||||
} | } | ||||
/** | |||||
* Returns the Gitlbit cookie in the request. | |||||
* | |||||
* @param request | |||||
* @return the Gitblit cookie for the request or null if not found | |||||
*/ | |||||
@Override | |||||
public String getCookie(HttpServletRequest request) { | |||||
if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) { | |||||
Cookie[] cookies = request.getCookies(); | |||||
if (cookies != null && cookies.length > 0) { | |||||
for (Cookie cookie : cookies) { | |||||
if (cookie.getName().equals(Constants.NAME)) { | |||||
String value = cookie.getValue(); | |||||
return value; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
/** | /** | ||||
* Sets a cookie for the specified user. | * Sets a cookie for the specified user. | ||||
* | * | ||||
} else { | } else { | ||||
// create real cookie | // create real cookie | ||||
userCookie = new Cookie(Constants.NAME, cookie); | userCookie = new Cookie(Constants.NAME, cookie); | ||||
userCookie.setMaxAge(Integer.MAX_VALUE); | |||||
// expire the cookie in 7 days | |||||
userCookie.setMaxAge((int) TimeUnit.DAYS.toSeconds(7)); | |||||
} | } | ||||
} | } | ||||
userCookie.setPath("/"); | userCookie.setPath("/"); |
return user; | return user; | ||||
} | } | ||||
@Override | |||||
public String getCookie(HttpServletRequest request) { | |||||
return authenticationManager.getCookie(request); | |||||
} | |||||
@Override | @Override | ||||
public void setCookie(HttpServletResponse response, UserModel user) { | public void setCookie(HttpServletResponse response, UserModel user) { | ||||
authenticationManager.setCookie(response, user); | authenticationManager.setCookie(response, user); |
*/ | */ | ||||
UserModel authenticate(String username, char[] password); | UserModel authenticate(String username, char[] password); | ||||
/** | |||||
* Returns the Gitlbit cookie in the request. | |||||
* | |||||
* @param request | |||||
* @return the Gitblit cookie for the request or null if not found | |||||
*/ | |||||
String getCookie(HttpServletRequest request); | |||||
/** | /** | ||||
* Sets a cookie for the specified user. | * Sets a cookie for the specified user. | ||||
* | * |
return; | return; | ||||
} | } | ||||
// change the cookie | |||||
userModel.cookie = StringUtils.getSHA1(userModel.username + password); | |||||
// Optionally store the password MD5 digest. | // Optionally store the password MD5 digest. | ||||
String type = app().settings().getString(Keys.realm.passwordStorage, "md5"); | String type = app().settings().getString(Keys.realm.passwordStorage, "md5"); | ||||
if (type.equalsIgnoreCase("md5")) { | if (type.equalsIgnoreCase("md5")) { |
package com.gitblit.wicket.pages; | package com.gitblit.wicket.pages; | ||||
import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||
import javax.servlet.http.HttpServletResponse; | |||||
import org.apache.wicket.PageParameters; | import org.apache.wicket.PageParameters; | ||||
import org.apache.wicket.markup.html.WebPage; | import org.apache.wicket.markup.html.WebPage; | ||||
import com.gitblit.Keys; | import com.gitblit.Keys; | ||||
import com.gitblit.models.UserModel; | import com.gitblit.models.UserModel; | ||||
import com.gitblit.utils.StringUtils; | |||||
import com.gitblit.wicket.GitBlitWebApp; | import com.gitblit.wicket.GitBlitWebApp; | ||||
import com.gitblit.wicket.GitBlitWebSession; | import com.gitblit.wicket.GitBlitWebSession; | ||||
// already have a session, refresh usermodel to pick up | // already have a session, refresh usermodel to pick up | ||||
// any changes to permissions or roles (issue-186) | // any changes to permissions or roles (issue-186) | ||||
UserModel user = app().users().getUserModel(session.getUser().username); | UserModel user = app().users().getUserModel(session.getUser().username); | ||||
// validate cookie during session (issue-361) | |||||
if (app().settings().getBoolean(Keys.web.allowCookieAuthentication, true)) { | |||||
HttpServletRequest request = ((WebRequest) getRequestCycle().getRequest()) | |||||
.getHttpServletRequest(); | |||||
String requestCookie = app().authentication().getCookie(request); | |||||
if (!StringUtils.isEmpty(requestCookie) && !StringUtils.isEmpty(user.cookie)) { | |||||
if (!requestCookie.equals(user.cookie)) { | |||||
// cookie was changed during our session | |||||
HttpServletResponse response = ((WebResponse) getRequestCycle().getResponse()) | |||||
.getHttpServletResponse(); | |||||
app().authentication().logout(response, user); | |||||
session.setUser(null); | |||||
session.invalidateNow(); | |||||
return; | |||||
} | |||||
} | |||||
} | |||||
session.setUser(user); | session.setUser(user); | ||||
return; | return; | ||||
} | } |