| /* |
| * Copyright (C) 2017 Thomas Wolf <thomas.wolf@paranor.ch> |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - 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. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. 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 COPYRIGHT OWNER 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. |
| */ |
| |
| package org.eclipse.jgit.http.test; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.EnumSet; |
| import java.util.List; |
| |
| import javax.servlet.DispatcherType; |
| import javax.servlet.Filter; |
| import javax.servlet.FilterChain; |
| import javax.servlet.FilterConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.jetty.servlet.FilterHolder; |
| import org.eclipse.jetty.servlet.ServletContextHandler; |
| import org.eclipse.jetty.servlet.ServletHolder; |
| import org.eclipse.jgit.errors.TransportException; |
| import org.eclipse.jgit.errors.UnsupportedCredentialItem; |
| import org.eclipse.jgit.http.server.GitServlet; |
| import org.eclipse.jgit.junit.TestRepository; |
| import org.eclipse.jgit.junit.http.AccessEvent; |
| import org.eclipse.jgit.junit.http.AppServer; |
| import org.eclipse.jgit.junit.http.HttpTestCase; |
| import org.eclipse.jgit.lib.ConfigConstants; |
| import org.eclipse.jgit.lib.NullProgressMonitor; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevBlob; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.transport.CredentialItem; |
| import org.eclipse.jgit.transport.CredentialsProvider; |
| import org.eclipse.jgit.transport.HttpTransport; |
| import org.eclipse.jgit.transport.Transport; |
| import org.eclipse.jgit.transport.URIish; |
| import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; |
| import org.eclipse.jgit.transport.http.HttpConnectionFactory; |
| import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory; |
| import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory; |
| import org.eclipse.jgit.util.HttpSupport; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| @RunWith(Parameterized.class) |
| public class SmartClientSmartServerSslTest extends HttpTestCase { |
| |
| // We run these tests with a server on localhost with a self-signed |
| // certificate. We don't do authentication tests here, so there's no need |
| // for username and password. |
| // |
| // But the server certificate will not validate. We know that Transport will |
| // ask whether we trust the server all the same. This credentials provider |
| // blindly trusts the self-signed certificate by answering "Yes" to all |
| // questions. |
| private CredentialsProvider testCredentials = new CredentialsProvider() { |
| |
| @Override |
| public boolean isInteractive() { |
| return false; |
| } |
| |
| @Override |
| public boolean supports(CredentialItem... items) { |
| for (CredentialItem item : items) { |
| if (item instanceof CredentialItem.InformationalMessage) { |
| continue; |
| } |
| if (item instanceof CredentialItem.YesNoType) { |
| continue; |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean get(URIish uri, CredentialItem... items) |
| throws UnsupportedCredentialItem { |
| for (CredentialItem item : items) { |
| if (item instanceof CredentialItem.InformationalMessage) { |
| continue; |
| } |
| if (item instanceof CredentialItem.YesNoType) { |
| ((CredentialItem.YesNoType) item).setValue(true); |
| continue; |
| } |
| return false; |
| } |
| return true; |
| } |
| }; |
| |
| private URIish remoteURI; |
| |
| private URIish secureURI; |
| |
| private RevBlob A_txt; |
| |
| private RevCommit A, B; |
| |
| @Parameters |
| public static Collection<Object[]> data() { |
| // run all tests with both connection factories we have |
| return Arrays.asList(new Object[][] { |
| { new JDKHttpConnectionFactory() }, |
| { new HttpClientConnectionFactory() } }); |
| } |
| |
| public SmartClientSmartServerSslTest(HttpConnectionFactory cf) { |
| HttpTransport.setConnectionFactory(cf); |
| } |
| |
| @Override |
| protected AppServer createServer() { |
| return new AppServer(0, 0); |
| } |
| |
| @Override |
| @Before |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| final TestRepository<Repository> src = createTestRepository(); |
| final String srcName = src.getRepository().getDirectory().getName(); |
| src.getRepository() |
| .getConfig() |
| .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, |
| ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); |
| |
| GitServlet gs = new GitServlet(); |
| |
| ServletContextHandler app = addNormalContext(gs, src, srcName); |
| |
| server.setUp(); |
| |
| remoteURI = toURIish(app, srcName); |
| secureURI = new URIish(rewriteUrl(remoteURI.toString(), "https", |
| server.getSecurePort())); |
| |
| A_txt = src.blob("A"); |
| A = src.commit().add("A_txt", A_txt).create(); |
| B = src.commit().parent(A).add("A_txt", "C").add("B", "B").create(); |
| src.update(master, B); |
| |
| src.update("refs/garbage/a/very/long/ref/name/to/compress", B); |
| } |
| |
| private ServletContextHandler addNormalContext(GitServlet gs, TestRepository<Repository> src, String srcName) { |
| ServletContextHandler app = server.addContext("/git"); |
| app.addFilter(new FilterHolder(new Filter() { |
| |
| @Override |
| public void init(FilterConfig filterConfig) |
| throws ServletException { |
| // empty |
| } |
| |
| // Redirects http to https for requests containing "/https/". |
| @Override |
| public void doFilter(ServletRequest request, |
| ServletResponse response, FilterChain chain) |
| throws IOException, ServletException { |
| final HttpServletResponse httpServletResponse = (HttpServletResponse) response; |
| final HttpServletRequest httpServletRequest = (HttpServletRequest) request; |
| final StringBuffer fullUrl = httpServletRequest.getRequestURL(); |
| if (httpServletRequest.getQueryString() != null) { |
| fullUrl.append("?") |
| .append(httpServletRequest.getQueryString()); |
| } |
| String urlString = rewriteUrl(fullUrl.toString(), "https", |
| server.getSecurePort()); |
| httpServletResponse |
| .setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); |
| httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, |
| urlString.replace("/https/", "/")); |
| } |
| |
| @Override |
| public void destroy() { |
| // empty |
| } |
| }), "/https/*", EnumSet.of(DispatcherType.REQUEST)); |
| app.addFilter(new FilterHolder(new Filter() { |
| |
| @Override |
| public void init(FilterConfig filterConfig) |
| throws ServletException { |
| // empty |
| } |
| |
| // Redirects https back to http for requests containing "/back/". |
| @Override |
| public void doFilter(ServletRequest request, |
| ServletResponse response, FilterChain chain) |
| throws IOException, ServletException { |
| final HttpServletResponse httpServletResponse = (HttpServletResponse) response; |
| final HttpServletRequest httpServletRequest = (HttpServletRequest) request; |
| final StringBuffer fullUrl = httpServletRequest.getRequestURL(); |
| if (httpServletRequest.getQueryString() != null) { |
| fullUrl.append("?") |
| .append(httpServletRequest.getQueryString()); |
| } |
| String urlString = rewriteUrl(fullUrl.toString(), "http", |
| server.getPort()); |
| httpServletResponse |
| .setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); |
| httpServletResponse.setHeader(HttpSupport.HDR_LOCATION, |
| urlString.replace("/back/", "/")); |
| } |
| |
| @Override |
| public void destroy() { |
| // empty |
| } |
| }), "/back/*", EnumSet.of(DispatcherType.REQUEST)); |
| gs.setRepositoryResolver(new TestRepositoryResolver(src, srcName)); |
| app.addServlet(new ServletHolder(gs), "/*"); |
| return app; |
| } |
| |
| @Test |
| public void testInitialClone_ViaHttps() throws Exception { |
| Repository dst = createBareRepository(); |
| assertFalse(dst.hasObject(A_txt)); |
| |
| try (Transport t = Transport.open(dst, secureURI)) { |
| t.setCredentialsProvider(testCredentials); |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| } |
| assertTrue(dst.hasObject(A_txt)); |
| assertEquals(B, dst.exactRef(master).getObjectId()); |
| fsck(dst, B); |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(2, requests.size()); |
| } |
| |
| @Test |
| public void testInitialClone_RedirectToHttps() throws Exception { |
| Repository dst = createBareRepository(); |
| assertFalse(dst.hasObject(A_txt)); |
| |
| URIish cloneFrom = extendPath(remoteURI, "/https"); |
| try (Transport t = Transport.open(dst, cloneFrom)) { |
| t.setCredentialsProvider(testCredentials); |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| } |
| assertTrue(dst.hasObject(A_txt)); |
| assertEquals(B, dst.exactRef(master).getObjectId()); |
| fsck(dst, B); |
| |
| List<AccessEvent> requests = getRequests(); |
| assertEquals(3, requests.size()); |
| } |
| |
| @Test |
| public void testInitialClone_RedirectBackToHttp() throws Exception { |
| Repository dst = createBareRepository(); |
| assertFalse(dst.hasObject(A_txt)); |
| |
| URIish cloneFrom = extendPath(secureURI, "/back"); |
| try (Transport t = Transport.open(dst, cloneFrom)) { |
| t.setCredentialsProvider(testCredentials); |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| fail("Should have failed (redirect from https to http)"); |
| } catch (TransportException e) { |
| assertTrue(e.getMessage().contains("not allowed")); |
| } |
| } |
| |
| @Test |
| public void testInitialClone_SslFailure() throws Exception { |
| Repository dst = createBareRepository(); |
| assertFalse(dst.hasObject(A_txt)); |
| |
| try (Transport t = Transport.open(dst, secureURI)) { |
| // Set a credentials provider that doesn't handle questions |
| t.setCredentialsProvider( |
| new UsernamePasswordCredentialsProvider("any", "anypwd")); |
| t.fetch(NullProgressMonitor.INSTANCE, mirror(master)); |
| fail("Should have failed (SSL certificate not trusted)"); |
| } catch (TransportException e) { |
| assertTrue(e.getMessage().contains("Secure connection")); |
| } |
| } |
| |
| } |