mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
merge with branch 1.x
This commit is contained in:
@@ -70,6 +70,10 @@ import sonia.scm.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.GroupModificationEvent;
|
||||
import sonia.scm.repository.RepositoryModificationEvent;
|
||||
import sonia.scm.user.UserModificationEvent;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -80,6 +84,9 @@ import java.util.Set;
|
||||
public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
{
|
||||
|
||||
// TODO move to util class
|
||||
private static final String SEPARATOR = System.getProperty("line.separator", "\n");
|
||||
|
||||
/** Field description */
|
||||
private static final String ADMIN_PERMISSION = "*";
|
||||
|
||||
@@ -139,10 +146,15 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Invalidates the cache of a user which was modified. The cache entries for the user will be invalidated for the
|
||||
* following reasons:
|
||||
* <ul>
|
||||
* <li>Admin or Active flag was modified.</li>
|
||||
* <li>New user created, for the case of old cache values</li>
|
||||
* <li>User deleted</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param event
|
||||
* @param event user event
|
||||
*/
|
||||
@Subscribe
|
||||
public void onEvent(UserEvent event)
|
||||
@@ -150,82 +162,160 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
if (event.getEventType().isPost())
|
||||
{
|
||||
User user = event.getItem();
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
String username = user.getId();
|
||||
if (event instanceof UserModificationEvent)
|
||||
{
|
||||
logger.debug(
|
||||
"clear cache of user {}, because user properties have changed",
|
||||
user.getName());
|
||||
User beforeModification = ((UserModificationEvent) event).getItemBeforeModification();
|
||||
if (shouldCacheBeCleared(user, beforeModification))
|
||||
{
|
||||
logger.debug("invalidate cache of user {}, because of a permission relevant field has changed", username);
|
||||
invalidateUserCache(username);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("cache of user {} is not invalidated, because no permission relevant field has changed", username);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("invalidate cache of user {}, because of user {} event", username, event.getEventType());
|
||||
invalidateUserCache(username);
|
||||
}
|
||||
|
||||
// check if this is neccessary
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCacheBeCleared(User user, User beforeModification)
|
||||
{
|
||||
return user.isAdmin() != beforeModification.isAdmin() || user.isActive() != beforeModification.isActive();
|
||||
}
|
||||
|
||||
private void invalidateUserCache(final String username)
|
||||
{
|
||||
cache.removeAll((CacheKey item) -> username.equalsIgnoreCase(item.username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Invalidates the whole cache, if a repository has changed. The cache get cleared for one of the following reasons:
|
||||
* <ul>
|
||||
* <li>New repository created</li>
|
||||
* <li>Repository was removed</li>
|
||||
* <li>Archived, Public readable or permission field of the repository was modified</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param event
|
||||
* @param event repository event
|
||||
*/
|
||||
@Subscribe
|
||||
public void onEvent(RepositoryEvent event)
|
||||
{
|
||||
if (event.getEventType().isPost())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
Repository repository = event.getItem();
|
||||
|
||||
if (event instanceof RepositoryModificationEvent)
|
||||
{
|
||||
logger.debug("clear cache, because repository {} has changed",
|
||||
event.getItem().getName());
|
||||
Repository beforeModification = ((RepositoryModificationEvent) event).getItemBeforeModification();
|
||||
if (shouldCacheBeCleared(repository, beforeModification))
|
||||
{
|
||||
logger.debug("clear cache, because a relevant field of repository {} has changed", repository.getName());
|
||||
cache.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug(
|
||||
"cache is not invalidated, because non relevant field of repository {} has changed",
|
||||
repository.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("clear cache, received {} event of repository {}", event.getEventType(), repository.getName());
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCacheBeCleared(Repository repository, Repository beforeModification)
|
||||
{
|
||||
return repository.isArchived() != beforeModification.isArchived()
|
||||
|| repository.isPublicReadable() != beforeModification.isPublicReadable()
|
||||
|| ! repository.getPermissions().equals(beforeModification.getPermissions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Invalidates the whole cache if a group permission has changed and invalidates the cached entries of a user, if a
|
||||
* user permission has changed.
|
||||
*
|
||||
*
|
||||
* @param event
|
||||
* @param event permission event
|
||||
*/
|
||||
@Subscribe
|
||||
public void onEvent(StoredAssignedPermissionEvent event)
|
||||
{
|
||||
if (event.getEventType().isPost())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
StoredAssignedPermission permission = event.getPermission();
|
||||
if (permission.isGroupPermission())
|
||||
{
|
||||
logger.debug("clear cache, because permission {} has changed",
|
||||
event.getPermission().getId());
|
||||
logger.debug("clear cache, because global group permission {} has changed", permission.getId());
|
||||
cache.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug(
|
||||
"clear cache of user {}, because permission {} has changed",
|
||||
permission.getName(), event.getPermission().getId()
|
||||
);
|
||||
invalidateUserCache(permission.getName());
|
||||
}
|
||||
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Invalidates the whole cache, if a group has changed. The cache get cleared for one of the following reasons:
|
||||
* <ul>
|
||||
* <li>New group created</li>
|
||||
* <li>Group was removed</li>
|
||||
* <li>Group members was modified</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param event
|
||||
* @param event group event
|
||||
*/
|
||||
@Subscribe
|
||||
public void onEvent(GroupEvent event)
|
||||
{
|
||||
if (event.getEventType().isPost())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
Group group = event.getItem();
|
||||
if (event instanceof GroupModificationEvent)
|
||||
{
|
||||
logger.debug("clear cache, because group {} has changed",
|
||||
event.getItem().getId());
|
||||
Group beforeModification = ((GroupModificationEvent) event).getItemBeforeModification();
|
||||
if (shouldCacheBeCleared(group, beforeModification))
|
||||
{
|
||||
logger.debug("clear cache, because group {} has changed", group.getId());
|
||||
cache.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug(
|
||||
"cache is not invalidated, because non relevant field of group {} has changed",
|
||||
group.getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("clear cache, received group event {} for group {}", event.getEventType(), group.getId());
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCacheBeCleared(Group group, Group beforeModification)
|
||||
{
|
||||
return !group.getMembers().equals(beforeModification.getMembers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -251,18 +341,13 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("collect AuthorizationInfo for user {}", user.getName());
|
||||
}
|
||||
|
||||
logger.trace("collect AuthorizationInfo for user {}", user.getName());
|
||||
info = createAuthorizationInfo(user, groupNames);
|
||||
cache.put(cacheKey, info);
|
||||
}
|
||||
else if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("retrieve AuthorizationInfo for user {} from cache",
|
||||
user.getName());
|
||||
logger.trace("retrieve AuthorizationInfo for user {} from cache", user.getName());
|
||||
}
|
||||
|
||||
return info;
|
||||
@@ -271,21 +356,8 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private void collectGlobalPermissions(Builder<String> builder,
|
||||
final User user, final GroupNames groups)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("collect global permissions for user {}", user.getName());
|
||||
}
|
||||
|
||||
List<StoredAssignedPermission> globalPermissions =
|
||||
securitySystem.getPermissions(new Predicate<AssignedPermission>()
|
||||
{
|
||||
|
||||
@Override
|
||||
public boolean apply(AssignedPermission input)
|
||||
{
|
||||
return isUserPermission(user, groups, input);
|
||||
}
|
||||
});
|
||||
securitySystem.getPermissions((AssignedPermission input) -> isUserPermitted(user, groups, input));
|
||||
|
||||
for (StoredAssignedPermission gp : globalPermissions)
|
||||
{
|
||||
@@ -301,12 +373,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
{
|
||||
for (Repository repository : repositoryDAO.getAll())
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("collect permissions for repository {} and user {}",
|
||||
repository.getName(), user.getName());
|
||||
}
|
||||
|
||||
collectRepositoryPermissions(builder, repository, user, groups);
|
||||
}
|
||||
}
|
||||
@@ -314,30 +380,36 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private void collectRepositoryPermissions(Builder<String> builder,
|
||||
Repository repository, User user, GroupNames groups)
|
||||
{
|
||||
List<sonia.scm.repository.Permission> repositoryPermissions =
|
||||
repository.getPermissions();
|
||||
List<sonia.scm.repository.Permission> repositoryPermissions
|
||||
= repository.getPermissions();
|
||||
|
||||
if (Util.isNotEmpty(repositoryPermissions))
|
||||
{
|
||||
|
||||
boolean hasPermission = false;
|
||||
for (sonia.scm.repository.Permission permission : repositoryPermissions)
|
||||
{
|
||||
if (isUserPermission(user, groups, permission))
|
||||
if (isUserPermitted(user, groups, permission))
|
||||
{
|
||||
|
||||
String perm = permission.getType().getPermissionPrefix().concat(
|
||||
repository.getId());
|
||||
|
||||
logger.trace("add repository permission {} for user {}", perm,
|
||||
user.getName());
|
||||
String perm = permission.getType().getPermissionPrefix().concat(repository.getId());
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("add repository permission {} for user {} at repository {}",
|
||||
perm, user.getName(), repository.getName());
|
||||
}
|
||||
|
||||
builder.add(perm);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission && logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("no permission for user {} defined at repository {}", user.getName(), repository.getName());
|
||||
}
|
||||
}
|
||||
else if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("repository {} has not permission entries",
|
||||
logger.trace("repository {} has no permission entries",
|
||||
repository.getName());
|
||||
}
|
||||
}
|
||||
@@ -371,19 +443,47 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
}
|
||||
|
||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
|
||||
|
||||
info.addStringPermissions(permissions);
|
||||
|
||||
|
||||
if (logger.isTraceEnabled()){
|
||||
logger.trace(createAuthorizationSummary(user, groups, info));
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private String createAuthorizationSummary(User user, GroupNames groups, AuthorizationInfo authzInfo)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder("authorization summary: ");
|
||||
|
||||
buffer.append(SEPARATOR).append("username : ").append(user.getName());
|
||||
buffer.append(SEPARATOR).append("groups : ");
|
||||
append(buffer, groups);
|
||||
buffer.append(SEPARATOR).append("roles : ");
|
||||
append(buffer, authzInfo.getRoles());
|
||||
buffer.append(SEPARATOR).append("permissions:");
|
||||
append(buffer, authzInfo.getStringPermissions());
|
||||
append(buffer, authzInfo.getObjectPermissions());
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private void append(StringBuilder buffer, Iterable<?> iterable){
|
||||
if (iterable != null){
|
||||
for ( Object item : iterable )
|
||||
{
|
||||
buffer.append(SEPARATOR).append(" - ").append(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
private boolean isUserPermission(User user, GroupNames groups,
|
||||
private boolean isUserPermitted(User user, GroupNames groups,
|
||||
PermissionObject perm)
|
||||
{
|
||||
//J-
|
||||
return (perm.isGroupPermission() && groups.contains(perm.getName()))
|
||||
return (perm.isGroupPermission() && groups.contains(perm.getName()))
|
||||
|| ((!perm.isGroupPermission()) && user.getName().equals(perm.getName()));
|
||||
//J+
|
||||
}
|
||||
@@ -443,7 +543,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private final String username;
|
||||
}
|
||||
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** authorization cache */
|
||||
|
||||
87
scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java
Normal file
87
scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.security;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Util methods to handle XsrfCookies.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @version 1.47
|
||||
*/
|
||||
public final class XsrfCookies
|
||||
{
|
||||
|
||||
private XsrfCookies()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new xsrf protection cookie and add it to the response.
|
||||
*
|
||||
* @param request http servlet request
|
||||
* @param response http servlet response
|
||||
* @param token xsrf token
|
||||
*/
|
||||
public static void create(HttpServletRequest request, HttpServletResponse response, String token){
|
||||
applyCookie(request, response, new Cookie(XsrfProtectionFilter.KEY, token));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current xsrf protection cookie from response.
|
||||
*
|
||||
* @param request http servlet request
|
||||
* @param response http servlet response
|
||||
*/
|
||||
public static void remove(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if ( cookies != null ){
|
||||
for ( Cookie c : cookies ){
|
||||
if ( XsrfProtectionFilter.KEY.equals(c.getName()) ){
|
||||
c.setMaxAge(0);
|
||||
c.setValue(null);
|
||||
applyCookie(request, response, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyCookie(HttpServletRequest request, HttpServletResponse response, Cookie cookie){
|
||||
cookie.setPath(request.getContextPath());
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import javax.inject.Singleton;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.filter.Filters;
|
||||
import sonia.scm.filter.WebElement;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.web.filter.HttpFilter;
|
||||
|
||||
/**
|
||||
* Xsrf protection http filter. The filter will issue an cookie with an xsrf protection token on the first ajax request
|
||||
* of the scm web interface and marks the http session as xsrf protected. On every other request within a protected
|
||||
* session, the web interface has to send the token from the cookie as http header on every request. If the filter
|
||||
* receives an request to a protected session, without proper xsrf header the filter will abort the request and send an
|
||||
* http error code back to the client. If the filter receives an request to a non protected session, from a non web
|
||||
* interface client the filter will call the chain. The {@link XsrfProtectionFilter} is disabled by default and can be
|
||||
* enabled with {@link ScmConfiguration#setEnabledXsrfProtection(boolean)}.
|
||||
*
|
||||
* TODO for scm-manager 2 we have to store the csrf token as part of the jwt token instead of session.
|
||||
*
|
||||
* @see https://bitbucket.org/sdorra/scm-manager/issues/793/json-hijacking-vulnerability-cwe-116-cwe
|
||||
* @author Sebastian Sdorra
|
||||
* @version 1.47
|
||||
*/
|
||||
@WebElement(Filters.PATTERN_RESTAPI)
|
||||
@Priority(Filters.PRIORITY_PRE_BASEURL)
|
||||
public final class XsrfProtectionFilter extends HttpFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for XsrfProtectionFilter
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(XsrfProtectionFilter.class);
|
||||
|
||||
/**
|
||||
* Key used for session, header and cookie.
|
||||
*/
|
||||
static final String KEY = "X-XSRF-Token";
|
||||
|
||||
@Inject
|
||||
public XsrfProtectionFilter(ScmConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws
|
||||
IOException, ServletException
|
||||
{
|
||||
if (configuration.isEnabledXsrfProtection())
|
||||
{
|
||||
doXsrfProtection(request, response, chain);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.trace("xsrf protection is disabled, skipping check");
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private void doXsrfProtection(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws
|
||||
IOException, ServletException
|
||||
{
|
||||
HttpSession session = request.getSession(true);
|
||||
String storedToken = (String) session.getAttribute(KEY);
|
||||
if ( ! Strings.isNullOrEmpty(storedToken) ){
|
||||
String headerToken = request.getHeader(KEY);
|
||||
if ( storedToken.equals(headerToken) ){
|
||||
logger.trace("received valid xsrf protected request");
|
||||
chain.doFilter(request, response);
|
||||
} else {
|
||||
// is forbidden the correct status code?
|
||||
logger.warn("received request to a xsrf protected session without proper xsrf token");
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
} else if (HttpUtil.isWUIRequest(request)) {
|
||||
logger.debug("received wui request, mark session as xsrf protected and issue a new token");
|
||||
String token = createToken();
|
||||
session.setAttribute(KEY, token);
|
||||
XsrfCookies.create(request, response, token);
|
||||
chain.doFilter(request, response);
|
||||
} else {
|
||||
// handle non webinterface clients, which does not need xsrf protection
|
||||
logger.trace("received request to a non xsrf protected session");
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private String createToken()
|
||||
{
|
||||
// TODO create interface and use a better method
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
private ScmConfiguration configuration;
|
||||
}
|
||||
Reference in New Issue
Block a user