*/
package it.component;
+import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.projectDir;
+
import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.SonarRunner;
import it.Category4Suite;
import org.junit.experimental.categories.Category;
import util.QaOnly;
-import static org.assertj.core.api.Assertions.assertThat;
-import static util.ItUtils.projectDir;
-
@Category(QaOnly.class)
public class ProjectSearchTest {
SonarRunner build = SonarRunner.create(projectDir("shared/xoo-sample"));
orchestrator.executeBuild(build);
- String url = orchestrator.getServer().getUrl() + "/api/projects?key=sample&versions=true";
+ String url = orchestrator.getServer().getUrl() + "/api/projects/index?key=sample&versions=true";
HttpClient httpclient = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(url);
*/
package org.sonar.server.app;
+import static com.google.common.collect.FluentIterable.from;
+import static java.util.Arrays.asList;
+
import com.google.common.base.Predicates;
import java.util.HashSet;
import java.util.List;
import org.apache.catalina.startup.Tomcat;
import org.sonar.process.Props;
-import static com.google.common.collect.FluentIterable.from;
-import static java.util.Arrays.asList;
-
/**
* Configuration of Tomcat connectors
*/
public static final String HTTP_PROTOCOL = "HTTP/1.1";
public static final String AJP_PROTOCOL = "AJP/1.3";
public static final int MAX_HTTP_HEADER_SIZE_BYTES = 48 * 1024;
+ private static final int MAX_POST_SIZE = -1;
private TomcatConnectors() {
// only static stuff
connector = newConnector(props, HTTP_PROTOCOL, "http");
configureMaxHttpHeaderSize(connector);
connector.setPort(port);
+ connector.setMaxPostSize(MAX_POST_SIZE);
}
return connector;
}
*/
package org.sonar.server.app;
+import static java.lang.String.format;
+import static org.apache.commons.lang.StringUtils.isEmpty;
+
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
-import static java.lang.String.format;
-import static org.apache.commons.lang.StringUtils.isEmpty;
-
/**
* Configures Tomcat contexts:
* <ul>
context.setUseNaming(false);
context.setDelegate(true);
context.setJarScanner(new NullJarScanner());
+ context.setAllowCasualMultipartParsing(true);
return context;
} catch (ServletException e) {
throw new IllegalStateException("Fail to configure webapp from " + dir, e);
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform;
+
+import static java.lang.String.format;
+
+import java.io.IOException;
+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;
+
+public class RoutesFilter implements Filter {
+
+ private static final String EMPTY = "";
+ private static final String BATCH_WS = "/batch";
+ private static final String API_SOURCES_WS = "/api/sources";
+
+ @Override
+ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+ String path = request.getRequestURI().replaceFirst(request.getContextPath(), EMPTY);
+ if (path.startsWith(BATCH_WS + "/") && path.endsWith(".jar")) {
+ // Scanner is still using /batch/file.jar url
+ response.sendRedirect(format("%s%s/file?name=%s", request.getContextPath(), BATCH_WS, path.replace(BATCH_WS + "/", EMPTY)));
+ } else if ("/batch_bootstrap/index".equals(path)) {
+ // Scanner is still using /batch_bootstrap url
+ response.sendRedirect(format("%s%s/index", request.getContextPath(), BATCH_WS));
+ } else if (API_SOURCES_WS.equals(path)) {
+ // SONAR-7852 /api/sources?resource url is still used
+ response.sendRedirect(format("%s%s/index?%s", request.getContextPath(), API_SOURCES_WS, request.getQueryString()));
+ } else {
+ chain.doFilter(request, response);
+ }
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // Nothing
+ }
+
+ @Override
+ public void destroy() {
+ // Nothing
+ }
+}
import org.sonar.server.view.index.ViewIndexDefinition;
import org.sonar.server.view.index.ViewIndexer;
import org.sonar.server.ws.WebServiceEngine;
+import org.sonar.server.ws.WebServiceFilter;
import org.sonar.server.ws.WebServicesWs;
import org.sonar.server.ws.WsResponseCommonFormat;
// web services
WebServiceEngine.class,
WebServicesWs.class,
+ WebServiceFilter.class,
// localization
L10nWs.class,
import org.sonar.server.platform.ws.StatusAction;
import org.sonar.server.platform.ws.SystemWs;
import org.sonar.server.ws.WebServiceEngine;
+import org.sonar.server.ws.WebServiceFilter;
import org.sonar.server.ws.WebServicesWs;
public class PlatformLevelSafeMode extends PlatformLevel {
WebServicesWs.class,
// WS engine
- WebServiceEngine.class);
+ WebServiceEngine.class,
+ WebServiceFilter.class);
}
}
*/
package org.sonar.server.ws;
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static org.apache.commons.lang.StringUtils.substringAfterLast;
+
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HttpHeaders;
import java.io.InputStream;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.servlet.http.HttpServletRequest;
-import org.jruby.RubyFile;
+import javax.servlet.http.Part;
import org.sonar.api.server.ws.internal.ValidatingRequest;
import org.sonarqube.ws.MediaTypes;
-import static com.google.common.base.MoreObjects.firstNonNull;
-import static org.apache.commons.lang.StringUtils.substringAfterLast;
-
public class ServletRequest extends ValidatingRequest {
private final HttpServletRequest source;
- private final Map<String, Object> params;
static final Map<String, String> SUPPORTED_MEDIA_TYPES_BY_URL_SUFFIX = ImmutableMap.of(
"json", MediaTypes.JSON,
"protobuf", MediaTypes.PROTOBUF,
"text", MediaTypes.TXT);
- public ServletRequest(HttpServletRequest source, Map<String, Object> params) {
+ public ServletRequest(HttpServletRequest source) {
this.source = source;
- this.params = params;
}
@Override
@Override
public boolean hasParam(String key) {
- return source.getParameterMap().containsKey(key) || params.keySet().contains(key);
+ return source.getParameterMap().containsKey(key);
}
@Override
protected String readParam(String key) {
- String value = source.getParameter(key);
- if (value == null) {
- Object string = params.get(key);
- if (string != null && string instanceof String) {
- value = (String) string;
- }
- }
- return value;
+ return source.getParameter(key);
}
@Override
protected InputStream readInputStreamParam(String key) {
- Object file = params.get(key);
- if (file != null && file instanceof RubyFile) {
- return ((RubyFile) file).getInStream();
+ try {
+ Part part = source.getPart(key);
+ return part == null ? null : part.getInputStream();
+ } catch (Exception e) {
+ throw new IllegalStateException("Can't read file part", e);
}
- return null;
}
@Override
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.ws;
+
+import static java.lang.String.format;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+import org.sonar.api.server.ws.RailsHandler;
+import org.sonar.api.web.ServletFilter;
+
+/**
+ * This filter is used to execute Java WS.
+ *
+ * If the url match a Java WS, the output of the WS is returned and no other filers are executed.
+ * If the url doesn't match a Java WS, then it's calling remaining filters (for instance to execute Rails WS).
+ */
+public class WebServiceFilter extends ServletFilter {
+
+ private final WebServiceEngine webServiceEngine;
+ private final Map<String, WsUrl> wsUrls = new HashMap<>();
+ private final List<String> includeUrls = new ArrayList<>();
+
+ public WebServiceFilter(WebServiceEngine webServiceEngine) {
+ this.webServiceEngine = webServiceEngine;
+ webServiceEngine.controllers().stream()
+ .forEach(controller -> controller.actions().stream()
+ .filter(action -> !(action.handler() instanceof RailsHandler) && !(action.handler() instanceof ServletFilterHandler))
+ .forEach(action -> {
+ String url = "/" + action.path();
+ wsUrls.put(url, new WsUrl(controller.path(), action.key()));
+ includeUrls.add(url + "*");
+ }));
+ }
+
+ @Override
+ public UrlPattern doGetPattern() {
+ return UrlPattern.builder()
+ .includes(includeUrls)
+ .build();
+ }
+
+ @Override
+ public void doFilter(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) servletRequest;
+ HttpServletResponse response = (HttpServletResponse) servletResponse;
+ String path = request.getRequestURI().replaceFirst(request.getContextPath(), "");
+
+ String[] pathWithExtension = getPathWithExtension(path);
+ WsUrl url = wsUrls.get(pathWithExtension[0]);
+ if (url == null) {
+ throw new IllegalStateException(format("Unknown path : %s", path));
+ }
+ ServletRequest wsRequest = new ServletRequest(request);
+ ServletResponse wsResponse = new ServletResponse();
+ webServiceEngine.execute(wsRequest, wsResponse, url.getController(), url.getAction(), pathWithExtension[1]);
+ writeResponse(wsResponse, response);
+ }
+
+ private static void writeResponse(ServletResponse wsResponse, HttpServletResponse response) throws IOException {
+ // SONAR-6964 WS should not be cached by browser
+ response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+ for (String header : wsResponse.getHeaderNames()) {
+ response.setHeader(header, wsResponse.getHeader(header));
+ }
+
+ response.setContentType(wsResponse.stream().mediaType());
+ response.setStatus(wsResponse.stream().httpStatus());
+
+ OutputStream responseOutput = response.getOutputStream();
+ ByteArrayOutputStream wsOutputStream = (ByteArrayOutputStream) wsResponse.stream().output();
+ IOUtils.write(wsOutputStream.toByteArray(), responseOutput);
+ responseOutput.flush();
+ responseOutput.close();
+ }
+
+ private static String[] getPathWithExtension(String fullPath) {
+ String path = fullPath;
+ String extension = null;
+ int semiColonPos = fullPath.lastIndexOf('.');
+ if (semiColonPos > 0) {
+ path = fullPath.substring(0, semiColonPos);
+ extension = fullPath.substring(semiColonPos + 1);
+ }
+ return new String[] {path, extension};
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // Nothing to do
+ }
+
+ @Override
+ public void destroy() {
+ // Nothing to do
+ }
+
+ private static class WsUrl {
+ private final String controller;
+ private final String action;
+
+ WsUrl(String controller, String action) {
+ this.controller = controller;
+ this.action = action;
+ }
+
+ String getController() {
+ return controller;
+ }
+
+ String getAction() {
+ return action;
+ }
+ }
+}
*/
package org.sonar.server.app;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
import com.google.common.collect.ImmutableMap;
import java.net.InetAddress;
import java.util.Map;
import org.mockito.Mockito;
import org.sonar.process.Props;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
public class TomcatConnectorsTest {
Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
verifyConnectorProperty(tomcat, "http", "maxHttpHeaderSize", TomcatConnectors.MAX_HTTP_HEADER_SIZE_BYTES);
}
+ @Test
+ public void test_max_post_size_for_http_connection() throws Exception {
+ Properties properties = new Properties();
+
+ Props props = new Props(properties);
+ TomcatConnectors.configure(tomcat, props);
+ verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
+ @Override
+ public boolean matches(Object o) {
+ Connector c = (Connector) o;
+ return c.getMaxPostSize() == -1;
+ }
+ }));
+ }
+
private static void verifyConnectorProperty(Tomcat tomcat, final String connectorScheme,
- final String property, final Object propertyValue) {
+ final String property, final Object propertyValue) {
verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
public boolean matches(Object o) {
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.platform;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RoutesFilterTest {
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ FilterChain chain = mock(FilterChain.class);
+
+ RoutesFilter underTest = new RoutesFilter();
+
+ @Before
+ public void setUp() throws Exception {
+ when(request.getContextPath()).thenReturn("/sonarqube");
+ }
+
+ @Test
+ public void send_redirect_when_url_contains_batch_with_jar() throws Exception {
+ when(request.getRequestURI()).thenReturn("/batch/file.jar");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).sendRedirect("/sonarqube/batch/file?name=file.jar");
+ verifyZeroInteractions(chain);
+ }
+
+ @Test
+ public void send_redirect_when_url_contains_batch_bootstrap() throws Exception {
+ when(request.getRequestURI()).thenReturn("/batch_bootstrap/index");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).sendRedirect("/sonarqube/batch/index");
+ verifyZeroInteractions(chain);
+ }
+
+ @Test
+ public void send_redirect_when_url_contains_api_sources() throws Exception {
+ when(request.getRequestURI()).thenReturn("/api/sources");
+ when(request.getQueryString()).thenReturn("resource=my.project");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(response).sendRedirect("/sonarqube/api/sources/index?resource=my.project");
+ verifyZeroInteractions(chain);
+ }
+
+ @Test
+ public void does_not_redirect_and_execute_remaining_filter_on_unknown_path() throws Exception {
+ when(request.getRequestURI()).thenReturn("/api/issues/search");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(chain).doFilter(request, response);
+ verifyZeroInteractions(response);
+ }
+}
*/
package org.sonar.server.ws;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HttpHeaders;
-import java.util.Collections;
+import java.io.InputStream;
import javax.servlet.http.HttpServletRequest;
-import org.jruby.RubyFile;
+import javax.servlet.http.Part;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.sonarqube.ws.MediaTypes;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
public class ServletRequestTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
HttpServletRequest source = mock(HttpServletRequest.class);
@Test
public void call_method() {
- ServletRequest request = new ServletRequest(source, Collections.<String, Object>emptyMap());
+ ServletRequest request = new ServletRequest(source);
request.method();
verify(source).getMethod();
}
public void getMediaType() throws Exception {
when(source.getHeader(HttpHeaders.ACCEPT)).thenReturn(MediaTypes.JSON);
when(source.getRequestURI()).thenReturn("/path/to/resource/search");
- ServletRequest request = new ServletRequest(source, Collections.<String, Object>emptyMap());
+ ServletRequest request = new ServletRequest(source);
assertThat(request.getMediaType()).isEqualTo(MediaTypes.JSON);
}
@Test
public void default_media_type_is_octet_stream() throws Exception {
- ServletRequest request = new ServletRequest(source, Collections.<String, Object>emptyMap());
+ ServletRequest request = new ServletRequest(source);
when(source.getRequestURI()).thenReturn("/path/to/resource/search");
assertThat(request.getMediaType()).isEqualTo(MediaTypes.DEFAULT);
}
@Test
public void media_type_taken_in_url_first() throws Exception {
- ServletRequest request = new ServletRequest(source, Collections.<String, Object>emptyMap());
+ ServletRequest request = new ServletRequest(source);
when(source.getHeader(HttpHeaders.ACCEPT)).thenReturn(MediaTypes.JSON);
when(source.getRequestURI()).thenReturn("/path/to/resource/search.protobuf");
assertThat(request.getMediaType()).isEqualTo(MediaTypes.PROTOBUF);
@Test
public void has_param_from_source() {
when(source.getParameterMap()).thenReturn(ImmutableMap.of("param", new String[] {"value"}));
- ServletRequest request = new ServletRequest(source, Collections.<String, Object>emptyMap());
- assertThat(request.hasParam("param")).isTrue();
- }
-
- @Test
- public void has_param_from_params() {
- ServletRequest request = new ServletRequest(source, ImmutableMap.<String, Object>of("param", "value"));
+ ServletRequest request = new ServletRequest(source);
assertThat(request.hasParam("param")).isTrue();
}
@Test
public void read_param_from_source() {
when(source.getParameter("param")).thenReturn("value");
- ServletRequest request = new ServletRequest(source, Collections.<String, Object>emptyMap());
+ ServletRequest request = new ServletRequest(source);
assertThat(request.readParam("param")).isEqualTo("value");
}
@Test
- public void read_param_from_param() {
- ServletRequest request = new ServletRequest(source, ImmutableMap.<String, Object>of("param1", "value", "param2", 1));
- assertThat(request.readParam("param1")).isEqualTo("value");
- assertThat(request.readParam("param2")).isNull();
- assertThat(request.readParam("param3")).isNull();
+ public void read_input_stream() throws Exception {
+ InputStream file = mock(InputStream.class);
+ Part part = mock(Part.class);
+ when(part.getInputStream()).thenReturn(file);
+ when(source.getPart("param1")).thenReturn(part);
+
+ ServletRequest request = new ServletRequest(source);
+ assertThat(request.readInputStreamParam("param1")).isEqualTo(file);
+
+ assertThat(request.readInputStreamParam("param2")).isNull();
}
@Test
- public void read_input_stream() {
- RubyFile file = mock(RubyFile.class);
- ServletRequest request = new ServletRequest(source, ImmutableMap.<String, Object>of("param1", file, "param2", "value"));
+ public void throw_ISE_when_invalid_part() throws Exception {
+ InputStream file = mock(InputStream.class);
+ Part part = mock(Part.class);
+ when(part.getInputStream()).thenReturn(file);
+ doThrow(IllegalArgumentException.class).when(source).getPart("param1");
+ ServletRequest request = new ServletRequest(source);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Can't read file part");
request.readInputStreamParam("param1");
- verify(file).getInStream();
-
- assertThat(request.readInputStreamParam("param2")).isNull();
}
@Test
public void to_string() {
when(source.getRequestURL()).thenReturn(new StringBuffer("http:localhost:9000/api/issues"));
- ServletRequest request = new ServletRequest(source, Collections.<String, Object>emptyMap());
+ ServletRequest request = new ServletRequest(source);
assertThat(request.toString()).isEqualTo("http:localhost:9000/api/issues");
when(source.getQueryString()).thenReturn("components=sonar");
- request = new ServletRequest(source, Collections.<String, Object>emptyMap());
+ request = new ServletRequest(source);
assertThat(request.toString()).isEqualTo("http:localhost:9000/api/issues?components=sonar");
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+package org.sonar.server.ws;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.ws.WebServiceFilterTest.WsUrl.newWsUrl;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.RailsHandler;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.RequestHandler;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+
+public class WebServiceFilterTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ WebServiceEngine webServiceEngine = mock(WebServiceEngine.class);
+
+ HttpServletRequest request = mock(HttpServletRequest.class);
+ HttpServletResponse response = mock(HttpServletResponse.class);
+ FilterChain chain = mock(FilterChain.class);
+ ServletOutputStream responseOutput = mock(ServletOutputStream.class);
+
+ WebServiceFilter underTest;
+
+ @Before
+ public void setUp() throws Exception {
+ when(request.getContextPath()).thenReturn("");
+ when(response.getOutputStream()).thenReturn(responseOutput);
+ }
+
+ @Test
+ public void do_get_pattern() throws Exception {
+ initWebServiceEngine(
+ newWsUrl("api/issues", "search"),
+ newWsUrl("batch", "index"),
+ newWsUrl("api/authentication", "login").setHandler(ServletFilterHandler.INSTANCE),
+ newWsUrl("api/resources", "index").setHandler(RailsHandler.INSTANCE));
+
+ assertThat(underTest.doGetPattern().matches("/api/issues/search")).isTrue();
+ assertThat(underTest.doGetPattern().matches("/api/issues/search.protobuf")).isTrue();
+ assertThat(underTest.doGetPattern().matches("/batch/index")).isTrue();
+
+ assertThat(underTest.doGetPattern().matches("/api/resources/index")).isFalse();
+ assertThat(underTest.doGetPattern().matches("/api/authentication/login")).isFalse();
+ assertThat(underTest.doGetPattern().matches("/foo")).isFalse();
+ }
+
+ @Test
+ public void execute() throws Exception {
+ initWebServiceEngine(newWsUrl("api/issues", "search"));
+ when(request.getRequestURI()).thenReturn("/api/issues/search");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(webServiceEngine).execute(any(ServletRequest.class), any(org.sonar.server.ws.ServletResponse.class), eq("api/issues"), eq("search"), eq(null));
+
+ verify(response).setStatus(200);
+ verify(response).setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+
+ verify(responseOutput).flush();
+ verify(responseOutput).close();
+ verifyZeroInteractions(chain);
+ }
+
+ @Test
+ public void execute_with_extension() throws Exception {
+ initWebServiceEngine(newWsUrl("api/issues", "search"));
+ when(request.getRequestURI()).thenReturn("/api/issues/search.protobuff");
+
+ underTest.doFilter(request, response, chain);
+
+ verify(webServiceEngine).execute(any(ServletRequest.class), any(org.sonar.server.ws.ServletResponse.class), eq("api/issues"), eq("search"), eq("protobuff"));
+ }
+
+ @Test
+ public void fail_on_unknown_path() throws Exception {
+ initWebServiceEngine(newWsUrl("api/issues", "search"));
+ when(request.getRequestURI()).thenReturn("/api/resources/index");
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Unknown path : /api/resources/index");
+ underTest.doFilter(request, response, chain);
+ }
+
+ private void initWebServiceEngine(WsUrl... wsUrls) {
+ List<WebService.Controller> controllers = new ArrayList<>();
+
+ for (WsUrl wsUrl : wsUrls) {
+ String controller = wsUrl.getController();
+ WebService.Controller wsController = mock(WebService.Controller.class);
+ when(wsController.path()).thenReturn(controller);
+
+ List<WebService.Action> actions = new ArrayList<>();
+ for (String action : wsUrl.getActions()) {
+ WebService.Action wsAction = mock(WebService.Action.class);
+ when(wsAction.path()).thenReturn(controller + "/" + action);
+ when(wsAction.key()).thenReturn(action);
+ when(wsAction.handler()).thenReturn(wsUrl.getRequestHandler());
+ actions.add(wsAction);
+ }
+ when(wsController.actions()).thenReturn(actions);
+ controllers.add(wsController);
+ }
+ when(webServiceEngine.controllers()).thenReturn(controllers);
+ underTest = new WebServiceFilter(webServiceEngine);
+ }
+
+ static final class WsUrl {
+ private String controller;
+ private String[] actions;
+ private RequestHandler requestHandler = EmptyRequestHandler.INSTANCE;
+
+ WsUrl(String controller, String... actions) {
+ this.controller = controller;
+ this.actions = actions;
+ }
+
+ WsUrl setHandler(RequestHandler requestHandler) {
+ this.requestHandler = requestHandler;
+ return this;
+ }
+
+ String getController() {
+ return controller;
+ }
+
+ String[] getActions() {
+ return actions;
+ }
+
+ RequestHandler getRequestHandler() {
+ return requestHandler;
+ }
+
+ static WsUrl newWsUrl(String controller, String... actions) {
+ return new WsUrl(controller, actions);
+ }
+ }
+
+ private enum EmptyRequestHandler implements RequestHandler {
+ INSTANCE;
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ // Nothing to do
+ }
+ }
+
+}
# since 4.2
class Api::JavaWsController < Api::ApiController
- before_filter :check_authentication, :unless => ['skip_authentication_check_for_batch']
-
- # no need to check if WS can be accessed when DB is not up-to-date, this is dealt with in
- # Platform and ServerComponents classes
- skip_before_filter :check_database_version
-
- def index
- ws_request = Java::OrgSonarServerWs::ServletRequest.new(servlet_request, params.to_java)
- ws_response = Java::OrgSonarServerWs::ServletResponse.new()
- engine = Java::OrgSonarServerPlatform::Platform.component(Java::OrgSonarServerWs::WebServiceEngine.java_class)
- engine.execute(ws_request, ws_response, params[:wspath], params[:wsaction], params[:responseFormat])
-
- ws_response.getHeaderNames().to_a.each do |name|
- response.header[name] = ws_response.getHeader(name)
- end
-
- # response is already written to HttpServletResponse
- render :text => ws_response.stream().output().toByteArray(),
- :status => ws_response.stream().httpStatus(),
- :content_type => ws_response.stream().mediaType()
- end
-
def redirect_to_ws_listing
- redirect_to :action => 'index', :wspath => 'api/webservices', :wsaction => 'list'
- end
-
-
- def skip_authentication_check_for_batch
- (params[:wspath]=='batch' && params[:wsaction]=='index') ||
- (params[:wspath]=='batch' && params[:wsaction]=='file') ||
- (params[:wspath]=='api/system' && params[:wsaction]=='db_migration_status') ||
- (params[:wspath]=='api/system' && params[:wsaction]=='migrate_db') ||
- (params[:wspath]=='api/system' && params[:wsaction]=='status')
+ redirect_to :controller => 'api/webservices', :action => 'list'
end
end
map.connect 'api', :controller => 'api/java_ws', :action => 'redirect_to_ws_listing'
- # deprecated, sonar-runner should use batch/index and batch/file?name=xxx
- map.connect 'batch_bootstrap/index', :controller => 'api/java_ws', :action => 'index', :wspath => 'batch', :wsaction => 'index'
- map.connect 'batch/:name', :controller => 'api/java_ws', :action => 'index', :wspath => 'batch', :wsaction => 'file', :requirements => { :name => /.*/ }
-
map.connect 'api/server/:action', :controller => 'api/server'
map.connect 'api/resoures', :controller => 'api/resources', :action => 'index'
- map.connect 'api/sources', :controller => 'api/sources', :action => 'index'
map.resources 'properties', :path_prefix => 'api', :controller => 'api/properties', :requirements => { :id => /.*/ }
eval(ws.getTemplate())
prepend_route("api/plugins/#{ws.getId()}/:action/:id", {:controller => "api/#{ws.getId()}", :requirements => {:id => /.*/}})
end
-
- # Full Java web services
- ws_engine = Java::OrgSonarServerPlatform::Platform.component(Java::OrgSonarServerWs::WebServiceEngine.java_class)
- ws_engine.controllers().each do |controller|
- controller.actions.each do |action|
- if (!action.handler().java_kind_of?(Java::OrgSonarApiServerWs::RailsHandler))
- prepend_route("#{controller.path()}/#{action.key()}.:responseFormat/:id", {:controller => 'api/java_ws', :action => 'index', :wsaction => action.key(), :wspath => controller.path()})
- prepend_route("#{controller.path()}/#{action.key()}/:id", {:controller => 'api/java_ws', :action => 'index', :wsaction => action.key(), :wspath => controller.path()})
- if action.key()=='index'
- prepend_route("#{controller.path()}", {:controller => 'api/java_ws', :action => 'index', :wsaction => action.key(), :wspath => controller.path()})
- end
- end
- end
- end
end
end
end
<param-value>/images,/javascripts,/stylesheets</param-value>
</init-param>
</filter>
+ <filter>
+ <filter-name>RoutesFilter</filter-name>
+ <filter-class>org.sonar.server.platform.RoutesFilter</filter-class>
+ </filter>
<!-- order of execution is important -->
<filter-mapping>
<filter-name>ProfilingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
+ <filter-mapping>
+ <filter-name>RoutesFilter</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
<filter-mapping>
<filter-name>UserSessionFilter</filter-name>
<url-pattern>/*</url-pattern>