diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cacdf9a15..41136781ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Internal server error for git sub modules without tree object ([#1397](https://github.com/scm-manager/scm-manager/pull/1397)) - Do not expose subversion commit with id 0 ([#1395](https://github.com/scm-manager/scm-manager/pull/1395)) - Disable cloning repositories via ssh for anonymous users ([#1403](https://github.com/scm-manager/scm-manager/pull/1403)) +- Support anonymous file download through rest api for non-browser clients (e.g. curl or postman) when anonymous mode is set to protocol-only ([#1402](https://github.com/scm-manager/scm-manager/pull/1402)) - SVN diff with property changes ([#1400](https://github.com/scm-manager/scm-manager/pull/1400)) - Branches link in repository overview ([#1404](https://github.com/scm-manager/scm-manager/pull/1404)) diff --git a/scm-core/src/main/java/sonia/scm/web/UserAgent.java b/scm-core/src/main/java/sonia/scm/web/UserAgent.java index 9cee5f9c1e..0647587a19 100644 --- a/scm-core/src/main/java/sonia/scm/web/UserAgent.java +++ b/scm-core/src/main/java/sonia/scm/web/UserAgent.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- @@ -37,7 +37,7 @@ import static com.google.common.base.Preconditions.checkNotNull; //~--- JDK imports ------------------------------------------------------------ /** - * The software agent that is acting on behalf of a user. The user agent + * The software agent that is acting on behalf of a user. The user agent * represents a browser or one of the repository client (svn, git or hg). * * @author Sebastian Sdorra @@ -49,17 +49,16 @@ public final class UserAgent /** * Constructs a new user agent * - * - * @param name - * @param browser + * @param name * @param basicAuthenticationCharset + * @param browser */ - private UserAgent(String name, boolean browser, - Charset basicAuthenticationCharset) + private UserAgent(String name, Charset basicAuthenticationCharset, boolean browser, boolean scmClient) { this.name = checkNotNull(name); - this.browser = browser; this.basicAuthenticationCharset = checkNotNull(basicAuthenticationCharset); + this.browser = browser; + this.scmClient = scmClient; } //~--- methods -------------------------------------------------------------- @@ -71,8 +70,30 @@ public final class UserAgent * @param name name of the UserAgent * * @return builder for UserAgent + * + * @deprecated Use {@link #browser(String)}, {@link #scmClient(String)} or {@link #other(String)} instead */ + @Deprecated public static Builder builder(String name) + { + return other(name); + } + + public static Builder browser(String name) + { + final Builder builder = new Builder(name); + builder.browser = true; + return builder; + } + + public static Builder scmClient(String name) + { + final Builder builder = new Builder(name); + builder.scmClient = true; + return builder; + } + + public static Builder other(String name) { return new Builder(name); } @@ -127,7 +148,7 @@ public final class UserAgent //~--- get methods ---------------------------------------------------------- /** - * Returns the {@link Charset}, which is used to decode the basic + * Returns the {@link Charset}, which is used to decode the basic * authentication header. * * @return {@link Charset} for basic authentication @@ -152,13 +173,23 @@ public final class UserAgent * Returns {@code true} if UserAgent is a browser. * * - * @return {@code true} if UserAgent is a browser + * @return {@code true} if UserAgent is a browser */ public boolean isBrowser() { return browser; } + /** + * Returns {@code true} if UserAgent is an scm client (e.g. git, svn or hg). + * + * + * @return {@code true} if UserAgent is an scm client + */ + public boolean isScmClient() { + return scmClient; + } + //~--- inner classes -------------------------------------------------------- /** @@ -204,7 +235,10 @@ public final class UserAgent * @param browser {@code true} for a browser * * @return {@code this} + * + * @deprecated Use {@link #browser(String)} instead */ + @Deprecated public Builder browser(boolean browser) { this.browser = browser; @@ -215,12 +249,11 @@ public final class UserAgent /** * Builds the {@link UserAgent}. * - * * @return new {@link UserAgent} */ public UserAgent build() { - return new UserAgent(name, browser, basicAuthenticationCharset); + return new UserAgent(name, basicAuthenticationCharset, browser, scmClient); } //~--- fields ------------------------------------------------------------- @@ -229,7 +262,10 @@ public final class UserAgent private final String name; /** indicator for browsers */ - private boolean browser = true; + private boolean browser = false; + + /** indicator for browsers */ + private boolean scmClient = false; /** basic authentication charset */ private Charset basicAuthenticationCharset = Charsets.ISO_8859_1; @@ -244,6 +280,9 @@ public final class UserAgent /** indicator for browsers */ private final boolean browser; + /** indicator for scm clients (e.g. git, hg, svn) */ + private final boolean scmClient; + /** name of UserAgent */ private final String name; } diff --git a/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java b/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java index 423234f756..307e09fdca 100644 --- a/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java +++ b/scm-core/src/main/java/sonia/scm/web/UserAgentParser.java @@ -62,7 +62,7 @@ public final class UserAgentParser /** unknown UserAgent */ @VisibleForTesting - static final UserAgent UNKNOWN = UserAgent.builder("UNKNOWN").build(); + static final UserAgent UNKNOWN = UserAgent.other("UNKNOWN").build(); /** logger */ private static final Logger logger = diff --git a/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java b/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java index dd19414fe7..33ba090373 100644 --- a/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java +++ b/scm-core/src/test/java/sonia/scm/web/UserAgentParserTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- @@ -89,7 +89,7 @@ public class UserAgentParserTest UserAgent ua = parser.parse(UA_1); assertEquals(Charsets.ISO_8859_1, ua.getBasicAuthenticationCharset()); - assertTrue(ua.isBrowser()); + assertFalse(ua.isBrowser()); } /** @@ -99,11 +99,11 @@ public class UserAgentParserTest @Test public void testParse() { - UserAgent ua = UserAgent.builder("UA1").build(); + UserAgent ua = UserAgent.other("UA1").build(); when(provider1.parseUserAgent(UA_1)).thenReturn(ua); - UserAgent ua2 = UserAgent.builder("UA2").build(); + UserAgent ua2 = UserAgent.other("UA2").build(); when(provider2.parseUserAgent(UA_2)).thenReturn(ua2); @@ -120,7 +120,7 @@ public class UserAgentParserTest { when(request.getHeader(HttpUtil.HEADER_USERAGENT)).thenReturn(UA_2); - UserAgent ua = UserAgent.builder("UA2").build(); + UserAgent ua = UserAgent.other("UA2").build(); when(provider1.parseUserAgent(UA_2)).thenReturn(ua); assertEquals(ua, parser.parse(request)); @@ -144,7 +144,7 @@ public class UserAgentParserTest @Test public void testParseWithCache() { - UserAgent ua = UserAgent.builder("UA").build(); + UserAgent ua = UserAgent.other("UA").build(); when(cache.get(UA_1)).thenReturn(ua); assertEquals(ua, parser.parse(UA_1)); diff --git a/scm-core/src/test/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilterBaseTest.java b/scm-core/src/test/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilterBaseTest.java index 2c49458547..a3b9318e5a 100644 --- a/scm-core/src/test/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilterBaseTest.java +++ b/scm-core/src/test/java/sonia/scm/web/filter/HttpProtocolServletAuthenticationFilterBaseTest.java @@ -69,8 +69,8 @@ class HttpProtocolServletAuthenticationFilterBaseTest { @Mock private FilterChain filterChain; - private UserAgent nonBrowser = UserAgent.builder("i'm not a browser").browser(false).build(); - private UserAgent browser = UserAgent.builder("i am a browser").browser(true).build(); + private UserAgent nonBrowser = UserAgent.other("i'm not a browser").build(); + private UserAgent browser = UserAgent.browser("i am a browser").build(); @BeforeEach void setUpObjectUnderTest() { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java index 4bb2a74442..e93c5a3ecc 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitUserAgentProvider.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- @@ -40,36 +40,32 @@ import sonia.scm.plugin.Extension; */ @Extension public class GitUserAgentProvider implements UserAgentProvider { - + private static final String PREFIX_JGIT = "jgit/"; @VisibleForTesting - static final UserAgent JGIT = UserAgent.builder("JGit") - .browser(false) + static final UserAgent JGIT = UserAgent.scmClient("JGit") .basicAuthenticationCharset(Charsets.UTF_8) .build(); - + private static final String PREFIX_REGULAR = "git/"; - + @VisibleForTesting - static final UserAgent GIT = UserAgent.builder("Git") - .browser(false) + static final UserAgent GIT = UserAgent.scmClient("Git") .basicAuthenticationCharset(Charsets.UTF_8) .build(); - + private static final String PREFIX_LFS = "git-lfs/"; @VisibleForTesting - static final UserAgent GIT_LFS = UserAgent.builder("Git Lfs") - .browser(false) + static final UserAgent GIT_LFS = UserAgent.scmClient("Git Lfs") .basicAuthenticationCharset(Charsets.UTF_8) .build(); private static final String SUFFIX_MSYSGIT = "msysgit"; - + @VisibleForTesting - static final UserAgent MSYSGIT = UserAgent.builder("msysGit") - .browser(false) + static final UserAgent MSYSGIT = UserAgent.scmClient("msysGit") .basicAuthenticationCharset(Charsets.UTF_8) .build(); @@ -80,7 +76,7 @@ public class GitUserAgentProvider implements UserAgentProvider { @Override public UserAgent parseUserAgent(String userAgentString) { String lowerUserAgent = toLower(userAgentString); - + if (isJGit(lowerUserAgent)) { return JGIT; } else if (isMsysGit(lowerUserAgent)) { @@ -93,23 +89,23 @@ public class GitUserAgentProvider implements UserAgentProvider { return null; } } - + private String toLower(String value) { return Strings.nullToEmpty(value).toLowerCase(Locale.ENGLISH); } - + private boolean isJGit(String userAgent) { return userAgent.startsWith(PREFIX_JGIT); } - + private boolean isMsysGit(String userAgent) { return userAgent.startsWith(PREFIX_REGULAR) && userAgent.contains(SUFFIX_MSYSGIT); } - + private boolean isGitLFS(String userAgent) { return userAgent.startsWith(PREFIX_LFS); } - + private boolean isGit(String userAgent) { return userAgent.startsWith(PREFIX_REGULAR); } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUserAgentProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUserAgentProvider.java index 82dc543230..109c80a09a 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUserAgentProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/web/HgUserAgentProvider.java @@ -45,8 +45,7 @@ public class HgUserAgentProvider implements UserAgentProvider /** mercurial seems to use system encoding */ @VisibleForTesting - static UserAgent HG = UserAgent.builder("Mercurial").browser( - false).basicAuthenticationCharset( + static UserAgent HG = UserAgent.scmClient("Mercurial").basicAuthenticationCharset( Charset.defaultCharset()).build(); /** Field description */ diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnUserAgentProvider.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnUserAgentProvider.java index b5eb5c32e1..45b4e651ff 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnUserAgentProvider.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/web/SvnUserAgentProvider.java @@ -49,13 +49,13 @@ public final class SvnUserAgentProvider implements UserAgentProvider /** TortoiseSVN */ @VisibleForTesting static final UserAgent TORTOISE_SVN = - UserAgent.builder("TortoiseSVN").browser(false) + UserAgent.scmClient("TortoiseSVN") .basicAuthenticationCharset(Charsets.UTF_8).build(); /** Subversion cli client */ @VisibleForTesting static final UserAgent SVN = - UserAgent.builder("Subversion").browser(false) + UserAgent.scmClient("Subversion") .basicAuthenticationCharset(Charsets.UTF_8).build(); //~--- methods -------------------------------------------------------------- diff --git a/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java b/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java index 60650ff927..e9725b41d7 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java +++ b/scm-webapp/src/main/java/sonia/scm/web/BrowserUserAgentProvider.java @@ -41,7 +41,7 @@ public class BrowserUserAgentProvider implements UserAgentProvider /** Field description */ @VisibleForTesting - static final UserAgent CHROME = UserAgent.builder( + static final UserAgent CHROME = UserAgent.browser( "Chrome").basicAuthenticationCharset( Charsets.UTF_8).build(); @@ -50,21 +50,21 @@ public class BrowserUserAgentProvider implements UserAgentProvider /** Field description */ @VisibleForTesting - static final UserAgent FIREFOX = UserAgent.builder("Firefox").build(); + static final UserAgent FIREFOX = UserAgent.browser("Firefox").build(); /** Field description */ private static final String FIREFOX_PATTERN = "firefox"; /** Field description */ @VisibleForTesting - static final UserAgent MSIE = UserAgent.builder("Internet Explorer").build(); + static final UserAgent MSIE = UserAgent.browser("Internet Explorer").build(); /** Field description */ private static final String MSIE_PATTERN = "msie"; /** Field description */ @VisibleForTesting // todo check charset - static final UserAgent SAFARI = UserAgent.builder("Safari").build(); + static final UserAgent SAFARI = UserAgent.browser("Safari").build(); /** Field description */ private static final String OPERA_PATTERN = "opera"; @@ -74,7 +74,7 @@ public class BrowserUserAgentProvider implements UserAgentProvider /** Field description */ @VisibleForTesting // todo check charset - static final UserAgent OPERA = UserAgent.builder( + static final UserAgent OPERA = UserAgent.browser( "Opera").basicAuthenticationCharset( Charsets.UTF_8).build(); diff --git a/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java b/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java index cb48c329c2..84f4f75191 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java +++ b/scm-webapp/src/main/java/sonia/scm/web/protocol/HttpProtocolServlet.java @@ -74,10 +74,7 @@ public class HttpProtocolServlet extends HttpServlet { @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { UserAgent userAgent = userAgentParser.parse(request); - if (userAgent.isBrowser()) { - log.trace("dispatch browser request for user agent {}", userAgent); - dispatcher.dispatch(request, response, request.getRequestURI()); - } else { + if (userAgent.isScmClient()) { String pathInfo = request.getPathInfo(); Optional namespaceAndName = pathExtractor.fromUri(pathInfo); if (namespaceAndName.isPresent()) { @@ -86,6 +83,9 @@ public class HttpProtocolServlet extends HttpServlet { log.debug("namespace and name not found in request path {}", pathInfo); response.setStatus(HttpStatus.SC_BAD_REQUEST); } + } else { + log.trace("dispatch non-scm-client request for user agent {}", userAgent); + dispatcher.dispatch(request, response, request.getRequestURI()); } } diff --git a/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java b/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java index 7e01e0b1d4..9dac395df9 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/protocol/HttpProtocolServletTest.java @@ -91,15 +91,12 @@ class HttpProtocolServletTest { @BeforeEach void prepareMocks() { when(userAgentParser.parse(request)).thenReturn(userAgent); - when(userAgent.isBrowser()).thenReturn(true); + when(userAgent.isScmClient()).thenReturn(false); when(request.getRequestURI()).thenReturn("uri"); } @Test void shouldDispatchBrowserRequests() throws ServletException, IOException { - when(userAgent.isBrowser()).thenReturn(true); - when(request.getRequestURI()).thenReturn("uri"); - servlet.service(request, response); verify(dispatcher).dispatch(request, response, "uri"); @@ -113,7 +110,7 @@ class HttpProtocolServletTest { @BeforeEach void prepareMocks() { when(userAgentParser.parse(request)).thenReturn(userAgent); - when(userAgent.isBrowser()).thenReturn(false); + when(userAgent.isScmClient()).thenReturn(true); } @Test