mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-01 02:55:56 +01:00
prompt authentication again for failed subversion authentication and improved error message for missing privileges
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010, 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.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.tmatesoft.svn.core.SVNErrorCode;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class ScmSvnErrorCode extends SVNErrorCode
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final SVNErrorCode AUTHN_FAILED =
|
||||
new ScmSvnErrorCode(AUTHN_CATEGORY, 4, "Authentication failed");
|
||||
|
||||
/** Field description */
|
||||
public static final SVNErrorCode AUTHZ_NOT_ENOUGH_PRIVILEGES =
|
||||
new ScmSvnErrorCode(AUTHZ_CATEGORY, 4,
|
||||
"You do not have enough access privileges for this operation.");
|
||||
|
||||
/** Field description */
|
||||
private static final long serialVersionUID = -6864996390796610410L;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param category
|
||||
* @param index
|
||||
* @param description
|
||||
*/
|
||||
protected ScmSvnErrorCode(int category, int index, String description)
|
||||
{
|
||||
super(category, index, description);
|
||||
}
|
||||
}
|
||||
@@ -37,23 +37,37 @@ package sonia.scm.repository;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.io.Closeables;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.tmatesoft.svn.core.SVNErrorCode;
|
||||
import org.tmatesoft.svn.core.SVNLogEntry;
|
||||
import org.tmatesoft.svn.core.SVNLogEntryPath;
|
||||
import org.tmatesoft.svn.core.internal.io.dav.DAVElement;
|
||||
import org.tmatesoft.svn.core.internal.server.dav.DAVXMLUtil;
|
||||
import org.tmatesoft.svn.core.internal.util.SVNEncodingUtil;
|
||||
import org.tmatesoft.svn.core.internal.util.SVNXMLUtil;
|
||||
import org.tmatesoft.svn.core.io.SVNRepository;
|
||||
import org.tmatesoft.svn.core.wc.SVNClientManager;
|
||||
import org.tmatesoft.svn.core.wc.admin.SVNChangeEntry;
|
||||
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.util.Util;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -61,6 +75,12 @@ import java.util.Map;
|
||||
public final class SvnUtil
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
public static final String XML_CONTENT_TYPE = "text/xml; charset=\"utf-8\"";
|
||||
|
||||
/** Field description */
|
||||
private static final String HEADER_USERAGENT = "User-Agent";
|
||||
|
||||
/** Field description */
|
||||
private static final String ID_TRANSACTION_PREFIX = "-1:";
|
||||
|
||||
@@ -70,6 +90,9 @@ public final class SvnUtil
|
||||
*/
|
||||
private static final char TYPE_UPDATED = 'U';
|
||||
|
||||
/** Field description */
|
||||
private static final String USERAGENT_SVN = "svn/";
|
||||
|
||||
/**
|
||||
* the logger for SvnUtil
|
||||
*/
|
||||
@@ -232,6 +255,39 @@ public final class SvnUtil
|
||||
return changesets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
* @param errorCode
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String createErrorBody(SVNErrorCode errorCode)
|
||||
{
|
||||
StringBuffer xmlBuffer = new StringBuffer();
|
||||
|
||||
SVNXMLUtil.addXMLHeader(xmlBuffer);
|
||||
|
||||
List<String> namespaces = Lists.newArrayList(DAVElement.DAV_NAMESPACE,
|
||||
DAVElement.SVN_APACHE_PROPERTY_NAMESPACE);
|
||||
|
||||
SVNXMLUtil.openNamespaceDeclarationTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX,
|
||||
DAVXMLUtil.SVN_DAV_ERROR_TAG, namespaces, SVNXMLUtil.PREFIX_MAP,
|
||||
xmlBuffer);
|
||||
|
||||
SVNXMLUtil.openXMLTag(SVNXMLUtil.SVN_APACHE_PROPERTY_PREFIX,
|
||||
"human-readable", SVNXMLUtil.XML_STYLE_NORMAL, "errcode",
|
||||
String.valueOf(errorCode.getCode()), xmlBuffer);
|
||||
xmlBuffer.append(
|
||||
SVNEncodingUtil.xmlEncodeCDATA(errorCode.getDescription()));
|
||||
SVNXMLUtil.closeXMLTag(SVNXMLUtil.SVN_APACHE_PROPERTY_PREFIX,
|
||||
"human-readable", xmlBuffer);
|
||||
SVNXMLUtil.closeXMLTag(SVNXMLUtil.DAV_NAMESPACE_PREFIX,
|
||||
DAVXMLUtil.SVN_DAV_ERROR_TAG, xmlBuffer);
|
||||
|
||||
return xmlBuffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -266,6 +322,39 @@ public final class SvnUtil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
* @param statusCode
|
||||
* @param errorCode
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void sendError(HttpServletRequest request,
|
||||
HttpServletResponse response, int statusCode, SVNErrorCode errorCode)
|
||||
throws IOException
|
||||
{
|
||||
HttpUtil.drainBody(request);
|
||||
|
||||
response.setStatus(statusCode);
|
||||
response.setContentType(XML_CONTENT_TYPE);
|
||||
|
||||
PrintWriter writer = null;
|
||||
|
||||
try
|
||||
{
|
||||
writer = response.getWriter();
|
||||
writer.println(createErrorBody(errorCode));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Closeables.close(writer, true);
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -311,6 +400,20 @@ public final class SvnUtil
|
||||
return id.substring(ID_TRANSACTION_PREFIX.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static boolean isSvnClient(HttpServletRequest request)
|
||||
{
|
||||
return Strings.nullToEmpty(request.getHeader(HEADER_USERAGENT)).toLowerCase(
|
||||
Locale.ENGLISH).startsWith(USERAGENT_SVN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2010, 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.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.tmatesoft.svn.core.SVNErrorCode;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.ScmSvnErrorCode;
|
||||
import sonia.scm.repository.SvnUtil;
|
||||
import sonia.scm.web.filter.AutoLoginModule;
|
||||
import sonia.scm.web.filter.BasicAuthenticationFilter;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public class SvnBasicAuthenticationFilter extends BasicAuthenticationFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param configuration
|
||||
* @param autoLoginModules
|
||||
*/
|
||||
@Inject
|
||||
public SvnBasicAuthenticationFilter(ScmConfiguration configuration,
|
||||
Set<AutoLoginModule> autoLoginModules)
|
||||
{
|
||||
super(configuration, autoLoginModules);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Sends unauthorized instead of forbidden for svn clients, because the
|
||||
* svn client prompts again for authentication.
|
||||
*
|
||||
*
|
||||
* @param request http request
|
||||
* @param response http response
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
protected void sendFailedAuthenticationError(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws IOException
|
||||
{
|
||||
if (SvnUtil.isSvnClient(request))
|
||||
{
|
||||
HttpUtil.sendUnauthorized(response, configuration.getRealmDescription());
|
||||
}
|
||||
else
|
||||
{
|
||||
super.sendFailedAuthenticationError(request, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,15 +39,22 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.tmatesoft.svn.core.SVNErrorCode;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.RepositoryProvider;
|
||||
import sonia.scm.repository.ScmSvnErrorCode;
|
||||
import sonia.scm.repository.SvnUtil;
|
||||
import sonia.scm.web.filter.ProviderPermissionFilter;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -58,22 +65,16 @@ public class SvnPermissionFilter extends ProviderPermissionFilter
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static Set<String> WRITEMETHOD_SET = ImmutableSet.of("MKACTIVITY",
|
||||
"PROPPATCH", "PUT",
|
||||
"CHECKOUT", "MKCOL", "MOVE",
|
||||
"COPY", "DELETE", "LOCK",
|
||||
"UNLOCK", "MERGE");
|
||||
private static final Set<String> WRITEMETHOD_SET =
|
||||
ImmutableSet.of("MKACTIVITY", "PROPPATCH", "PUT", "CHECKOUT", "MKCOL",
|
||||
"MOVE", "COPY", "DELETE", "LOCK", "UNLOCK", "MERGE");
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param configuration
|
||||
* @param securityContextProvider
|
||||
* @param repository
|
||||
*/
|
||||
@Inject
|
||||
@@ -83,6 +84,33 @@ public class SvnPermissionFilter extends ProviderPermissionFilter
|
||||
super(configuration, repository);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param request
|
||||
* @param response
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
protected void sendNotEnoughPrivilegesError(HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
throws IOException
|
||||
{
|
||||
if (SvnUtil.isSvnClient(request))
|
||||
{
|
||||
SvnUtil.sendError(request, response, HttpServletResponse.SC_FORBIDDEN,
|
||||
ScmSvnErrorCode.AUTHZ_NOT_ENOUGH_PRIVILEGES);
|
||||
}
|
||||
else
|
||||
{
|
||||
super.sendNotEnoughPrivilegesError(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,7 +69,7 @@ public class SvnServletModule extends ServletModule
|
||||
protected void configureServlets()
|
||||
{
|
||||
filter(PATTERN_SVN).through(SvnGZipFilter.class);
|
||||
filter(PATTERN_SVN).through(BasicAuthenticationFilter.class);
|
||||
filter(PATTERN_SVN).through(SvnBasicAuthenticationFilter.class);
|
||||
filter(PATTERN_SVN).through(SvnPermissionFilter.class);
|
||||
|
||||
Map<String, String> parameters = new HashMap<String, String>();
|
||||
|
||||
Reference in New Issue
Block a user