/*******************************************************************************
* Copyright (c) 2013, 2014 IBM Corporation and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.server.tests.servlets.site;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.orion.internal.server.core.metastore.SimpleMetaStore;
import org.eclipse.orion.internal.server.hosting.SiteConfigurationConstants;
import org.eclipse.orion.internal.server.servlets.workspace.authorization.AuthorizationService;
import org.eclipse.orion.server.core.ProtocolConstants;
import org.eclipse.orion.server.core.metastore.UserInfo;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.xml.sax.SAXException;
import com.meterware.httpunit.GetMethodWebRequest;
import com.meterware.httpunit.PutMethodWebRequest;
import com.meterware.httpunit.WebConversation;
import com.meterware.httpunit.WebRequest;
import com.meterware.httpunit.WebResponse;
/**
* Basic tests:
* - Start, stop a site
* - Access workspace files through paths on the running site
* - Access a remote URL through paths on the running site
* - Test starting site via PUT and POST (start on creation)
*
* Security tests:
* - Try to walk the workspace using ../ in hosted site path (should 404)
*
* Concurrency tests (done concurrently on many threads)
* - Create a file with unique name
* - Create a new site that exposes the file
* - Start the site
* - Access the site, verify file content.
* - Stop the site
* - Attempt to access the file again, verify that request 404s (or times out?)
*/
public class HostingTest extends CoreSiteTest {
private String workspaceId;
@BeforeClass
public static void setUpWorkspace() {
initializeWorkspaceLocation();
}
@Before
/**
* Before each test, create a workspace and prepare fields for use by test methods.
*/
public void setUp() throws Exception {
webConversation = new WebConversation();
webConversation.setExceptionsThrownOnErrorStatus(false);
setUpAuthorization();
createWorkspace(SimpleMetaStore.DEFAULT_WORKSPACE_NAME);
createTestProject(testName.getMethodName());
workspaceId = new Path(testProjectBaseLocation).segment(0);
}
@Test
public void testStartSite() throws SAXException, IOException, JSONException, URISyntaxException {
JSONArray mappings = makeMappings(new String[][] {{"/", "/A/bogusWorkspacePath"}});
WebResponse siteResp = createSite("Fizz site", workspaceId, mappings, "fizzsite", null);
JSONObject siteObject = new JSONObject(siteResp.getText());
String location = siteObject.getString(ProtocolConstants.HEADER_LOCATION);
startSite(location);
}
@Test
public void testStartSiteNoMappings() throws SAXException, IOException, JSONException, URISyntaxException {
// Empty array
JSONArray mappings = makeMappings(new String[0][0]);
WebResponse siteResp = createSite("Empty mappings site", workspaceId, mappings, "empty", null);
JSONObject siteObject = new JSONObject(siteResp.getText());
String location = siteObject.getString(ProtocolConstants.HEADER_LOCATION);
startSite(location);
// No mappings at all
WebResponse siteResp2 = createSite("Null mappings site", workspaceId, null, "null", null);
JSONObject siteObject2 = new JSONObject(siteResp2.getText());
String location2 = siteObject2.getString(ProtocolConstants.HEADER_LOCATION);
startSite(location2);
}
@Test
public void testStopSite() throws SAXException, IOException, JSONException, URISyntaxException {
JSONArray mappings = makeMappings(new String[][] {{"/", "/A/bogusWorkspacePath"}});
WebResponse siteResp = createSite("Buzz site", workspaceId, mappings, "buzzsite", null);
JSONObject siteObject = new JSONObject(siteResp.getText());
String location = siteObject.getString(ProtocolConstants.HEADER_LOCATION);
startSite(location);
stopSite(location);
}
@Test
/**
* Tests accessing a workspace file <del>and remote URL</del> that are part of a running site.
*/
public void testSiteAccess() throws SAXException, IOException, JSONException, URISyntaxException {
// Create file in workspace
final String filename = "foo.html";
final String fileContent = "<html><body>This is a test file</body></html>";
createFileOnServer("", filename, fileContent);
// Create a site that exposes the workspace file
final String siteName = "My hosted site";
//make absolute by adding test project path
IPath path = new Path(URI.create(makeResourceURIAbsolute(filename)).getPath());
while (path.segmentCount() != 0 && !path.segment(0).equals("file")) {
if (path.segment(0).equals("file")) {
break;
}
path = path.removeFirstSegments(1);
}
String filePath = path.toString();
if (filePath.startsWith("/")) {
filePath = filePath.substring(1);
}
//remove file segment to get the servlet-relative path
Assert.assertTrue(filePath.startsWith("file/"));
filePath = filePath.substring(5);
final String mountAt = "/file.html";
final JSONArray mappings = makeMappings(new String[][] {{mountAt, filePath}});
WebRequest createSiteReq = getCreateSiteRequest(siteName, workspaceId, mappings, null);
WebResponse createSiteResp = webConversation.getResponse(createSiteReq);
assertEquals(HttpURLConnection.HTTP_CREATED, createSiteResp.getResponseCode());
JSONObject siteObject = new JSONObject(createSiteResp.getText());
// Start the site
String location = siteObject.getString(ProtocolConstants.HEADER_LOCATION);
siteObject = startSite(location);
final JSONObject hostingStatus = siteObject.getJSONObject(SiteConfigurationConstants.KEY_HOSTING_STATUS);
final String hostedURL = hostingStatus.getString(SiteConfigurationConstants.KEY_HOSTING_STATUS_URL);
// Access the workspace file through the site
WebRequest getFileReq = new GetMethodWebRequest(hostedURL + mountAt);
WebResponse getFileResp = webConversation.getResponse(getFileReq);
assertEquals(fileContent, getFileResp.getText());
// Stop the site
stopSite(location);
// Check that the workspace file can't be accessed anymore
WebRequest getFile404Req = new GetMethodWebRequest(hostedURL + mountAt);
WebResponse getFile404Resp = webConversation.getResponse(getFile404Req);
assertEquals(HttpURLConnection.HTTP_NOT_FOUND, getFile404Resp.getResponseCode());
assertEquals("no-cache", getFile404Resp.getHeaderField("Cache-Control").toLowerCase());
}
@Test
/**
* Tests accessing a workspace file and that it has the expected MIME type.
*/
public void testSiteFileMime() throws SAXException, IOException, JSONException, URISyntaxException {
// Expose a directory named my.css and ensure it doesn't get "text/css" content-type
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=389252
final String filename = "foo.html";
final String fileContent = "<html><body>This is a test file</body></html>";
final String dirName = "my.css";
createDirectoryOnServer(dirName);
createFileOnServer(dirName + "/", filename, fileContent);
IPath path = new Path(URI.create(makeResourceURIAbsolute(filename)).getPath());
while (path.segmentCount() != 0 && !path.segment(0).equals("file")) {
if (path.segment(0).equals("file")) {
break;
}
path = path.removeFirstSegments(1);
}
String filePath = path.toString();
if (filePath.startsWith("/")) {
filePath = filePath.substring(1);
}
Assert.assertTrue(filePath.startsWith("file/"));
filePath = filePath.substring(5);
final String mountAt = "/";
final JSONArray mappings = makeMappings(new String[][] {{mountAt, filePath}});
WebRequest createSiteReq = getCreateSiteRequest("testMime", workspaceId, mappings, null);
WebResponse createSiteResp = webConversation.getResponse(createSiteReq);
assertEquals(HttpURLConnection.HTTP_CREATED, createSiteResp.getResponseCode());
JSONObject siteObject = new JSONObject(createSiteResp.getText());
// Start site
String location = siteObject.getString(ProtocolConstants.HEADER_LOCATION);
siteObject = startSite(location);
// Access the directory through the site
final JSONObject hostingStatus = siteObject.getJSONObject(SiteConfigurationConstants.KEY_HOSTING_STATUS);
final String hostedURL = hostingStatus.getString(SiteConfigurationConstants.KEY_HOSTING_STATUS_URL);
WebRequest getFileReq = new GetMethodWebRequest(hostedURL + mountAt);
WebResponse getFileResp = webConversation.getResponse(getFileReq);
assertEquals(true, getFileResp.getText().contains(filename));
assertEquals(false, "text/html".equals(getFileResp.getHeaderField("Content-Type").toLowerCase()));
// Stop the site
stopSite(location);
}
@Test
// Test for https://bugs.eclipse.org/bugs/show_bug.cgi?id=382760
public void testDisallowedSiteAccess() throws SAXException, IOException, JSONException, URISyntaxException, CoreException {
UserInfo userBObject = createUser("userB", "userB");
String userB = userBObject.getUniqueId();
// User "test": create file in test's workspace
final String filename = "foo.html";
final String fileContent = "<html><body>This is a test file</body></html>";
WebResponse createdFile = createFileOnServer("", filename, fileContent);
URL fileLocation = createdFile.getURL();
IPath filepath = new Path(fileLocation.getPath());
filepath = filepath.removeFirstSegments(new Path(FILE_SERVLET_LOCATION).segmentCount()); // chop off leading /file/
filepath = filepath.removeLastSegments(1); // chop off trailing /foo.html
filepath = filepath.makeAbsolute();
filepath = filepath.addTrailingSeparator();
String parentFolder = filepath.toString();
// User B: Give access to test's workspace
String bWorkspaceId = workspaceId;
AuthorizationService.addUserRight(userBObject.getUniqueId(), "/workspace/" + bWorkspaceId);
AuthorizationService.addUserRight(userBObject.getUniqueId(), "/workspace/" + bWorkspaceId + "/*");
// User B: create a site against B's workspace that exposes a file in test's workspace
final String siteName = "My hosted site";
final String filePath = parentFolder; //"/" + filename;
final String mountAt = "/"; //"/file.html";
final JSONArray mappings = makeMappings(new String[][] {{mountAt, filePath}});
WebRequest createSiteReq = getCreateSiteRequest(siteName, bWorkspaceId, mappings, null);
setAuthentication(createSiteReq, userB, userB);
WebResponse createSiteResp = webConversation.getResponse(createSiteReq);
assertEquals(HttpURLConnection.HTTP_CREATED, createSiteResp.getResponseCode());
JSONObject siteObject = new JSONObject(createSiteResp.getText());
// User B: Start the site
String location = siteObject.getString(ProtocolConstants.HEADER_LOCATION);
siteObject = startSite(location, userB, userB);
final JSONObject hostingStatus = siteObject.getJSONObject(SiteConfigurationConstants.KEY_HOSTING_STATUS);
final String hostedURL = hostingStatus.getString(SiteConfigurationConstants.KEY_HOSTING_STATUS_URL);
// Attempt to access file on user B's site, should fail
WebRequest getFileReq = new GetMethodWebRequest(hostedURL + mountAt);
WebResponse getFileResp = webConversation.getResponse(getFileReq);
assertEquals(HttpURLConnection.HTTP_FORBIDDEN, getFileResp.getResponseCode());
}
@Test
public void testRemoteProxyRequest() throws SAXException, IOException, JSONException, URISyntaxException {
final String siteName = "My remote hosting site";
final String remoteRoot = "/remoteWeb", remotePrefPath = "/remotePref", remoteFilePath = "/remoteFile";
final JSONArray mappings = makeMappings(new String[][] { {remoteRoot, SERVER_LOCATION}, {remoteFilePath, SERVER_LOCATION + FILE_SERVLET_LOCATION}, {remotePrefPath, SERVER_LOCATION + "/prefs"}});
WebRequest createSiteReq = getCreateSiteRequest(siteName, workspaceId, mappings, null);
WebResponse createSiteResp = webConversation.getResponse(createSiteReq);
assertEquals(HttpURLConnection.HTTP_CREATED, createSiteResp.getResponseCode());
JSONObject siteObject = new JSONObject(createSiteResp.getText());
// Start the site
String location = siteObject.getString(ProtocolConstants.HEADER_LOCATION);
siteObject = startSite(location);
final JSONObject hostingStatus = siteObject.getJSONObject(SiteConfigurationConstants.KEY_HOSTING_STATUS);
final String hostedURL = hostingStatus.getString(SiteConfigurationConstants.KEY_HOSTING_STATUS_URL);
// Access the remote URL through the site
WebRequest getRemoteUrlReq = new GetMethodWebRequest(hostedURL + remoteRoot);
WebResponse getRemoteUrlResp = webConversation.getResource(getRemoteUrlReq);
final String lowerCaseContent = getRemoteUrlResp.getText().toLowerCase();
assertEquals("Looks like our orion jetty server", true, lowerCaseContent.contains("orion") || lowerCaseContent.contains("jetty"));
// Test that we can invoke the Orion file API through the site, to create a file
final String fileName = "fizz.txt";
final String fileContent = "Created through a site";
createFileOnServer(hostedURL + remoteFilePath + testProjectBaseLocation, fileName, fileContent);
// Bugs 369813, 366098, 369811, 390732: ensure query parameters are passed through the site unmangled
// For this we'll call the 'prefs' API which uses query parameters
String prefKey = "foo[-]bar baz+quux";
String prefValue = "pref value";
String remotePrefUrl = hostedURL + remotePrefPath + "/user/myprefs";
WebRequest putPrefReq = createSetPreferenceRequest(remotePrefUrl, prefKey, prefValue);
setAuthentication(putPrefReq);
WebResponse putPrefResp = webConversation.getResponse(putPrefReq);
assertEquals(HttpURLConnection.HTTP_NO_CONTENT, putPrefResp.getResponseCode());
// Check pref value
WebRequest getPrefReq = new GetMethodWebRequest(remotePrefUrl + "?key=" + URLEncoder.encode(prefKey, "UTF-8"));
setAuthentication(getPrefReq);
WebResponse getPrefResp = webConversation.getResponse(getPrefReq);
assertEquals(HttpURLConnection.HTTP_OK, getPrefResp.getResponseCode());
JSONObject prefObject = new JSONObject(getPrefResp.getText());
assertEquals("Pref obtained through site has correct value", prefObject.optString(prefKey), prefValue);
// Stop the site
stopSite(location);
// Check that remote URL can't be accessed anymore
WebRequest getRemoteUrl404Req = new GetMethodWebRequest(hostedURL + remoteRoot);
WebResponse getRemoteUrl404Resp = webConversation.getResponse(getRemoteUrl404Req);
assertEquals(HttpURLConnection.HTTP_NOT_FOUND, getRemoteUrl404Resp.getResponseCode());
}
@Test
public void testRemoteProxyRequestPathEncoding() throws SAXException, IOException, JSONException, URISyntaxException {
final String siteName = "Site";
final String remoteFilePath = "/file";
final JSONArray mappings = makeMappings(new String[][] {{remoteFilePath, SERVER_LOCATION + FILE_SERVLET_LOCATION}});
WebRequest createSiteReq = getCreateSiteRequest(siteName, workspaceId, mappings, null);
WebResponse createSiteResp = webConversation.getResponse(createSiteReq);
assertEquals(HttpURLConnection.HTTP_CREATED, createSiteResp.getResponseCode());
JSONObject siteObject = new JSONObject(createSiteResp.getText());
// Start the site
String location = siteObject.getString(ProtocolConstants.HEADER_LOCATION);
siteObject = startSite(location);
final JSONObject hostingStatus = siteObject.getJSONObject(SiteConfigurationConstants.KEY_HOSTING_STATUS);
final String hostedURL = hostingStatus.getString(SiteConfigurationConstants.KEY_HOSTING_STATUS_URL);
// Test that we can invoke the Orion file API through the site, to create a file with a funny name
final String fileName = "a%b.txt";
final String fileContent = "Created through a site";
createFileOnServer(hostedURL + remoteFilePath + testProjectBaseLocation, fileName, fileContent);
// Bug 421995 test that file contents can be got
String fileURLOnSite = hostedURL + remoteFilePath + testProjectBaseLocation + "/" + URLEncoder.encode(fileName, "UTF-8");
WebRequest getFileReq = getGetFilesRequest(fileURLOnSite);
setAuthentication(getFileReq);
WebResponse getContentsResp = webConversation.getResponse(getFileReq);
assertEquals(HttpURLConnection.HTTP_OK, getContentsResp.getResponseCode());
assertEquals(fileContent, getContentsResp.getText());
// Stop the site
stopSite(location);
}
/**
* Starts the site at <code>siteLocation</code>, and asserts that it was started.
* @throws URISyntaxException
* @returns The JSON representation of the started site.
*/
private JSONObject startSite(String siteLocation, String user, String password) throws JSONException, IOException, SAXException, URISyntaxException {
JSONObject hostingStatus = new JSONObject();
hostingStatus.put(SiteConfigurationConstants.KEY_HOSTING_STATUS_STATUS, "started");
WebRequest launchSiteReq = getUpdateSiteRequest(siteLocation, null, null, null, null, hostingStatus);
if (user != null && password != null)
setAuthentication(launchSiteReq, user, password);
WebResponse launchSiteResp = webConversation.getResponse(launchSiteReq);
assertEquals(launchSiteResp.getText(), HttpURLConnection.HTTP_OK, launchSiteResp.getResponseCode());
// Check that it's started
JSONObject siteObject = new JSONObject(launchSiteResp.getText());
hostingStatus = siteObject.getJSONObject(SiteConfigurationConstants.KEY_HOSTING_STATUS);
assertEquals("started", hostingStatus.getString(SiteConfigurationConstants.KEY_HOSTING_STATUS_STATUS));
return siteObject;
}
private JSONObject startSite(String siteLocation) throws JSONException, IOException, SAXException, URISyntaxException {
return startSite(siteLocation, null, null);
}
/**
* Stops the site at <code>siteLocation</code>, and asserts that it was stopped.
* @throws URISyntaxException
*/
private void stopSite(final String siteLocation) throws JSONException, IOException, SAXException, URISyntaxException {
JSONObject hostingStatus = new JSONObject();
hostingStatus.put(SiteConfigurationConstants.KEY_HOSTING_STATUS_STATUS, "stopped");
WebRequest stopReq = getUpdateSiteRequest(siteLocation, null, null, null, null, hostingStatus);
WebResponse stopResp = webConversation.getResponse(stopReq);
assertEquals(HttpURLConnection.HTTP_OK, stopResp.getResponseCode());
// Check that it's stopped
JSONObject siteObject = new JSONObject(stopResp.getText());
hostingStatus = siteObject.getJSONObject(SiteConfigurationConstants.KEY_HOSTING_STATUS);
assertEquals("stopped", hostingStatus.getString(SiteConfigurationConstants.KEY_HOSTING_STATUS_STATUS));
}
private void createDirectoryOnServer(String dirname) throws SAXException, IOException, JSONException {
webConversation.setExceptionsThrownOnErrorStatus(false);
WebRequest createDirReq = getPostFilesRequest("", getNewDirJSON(dirname).toString(), dirname);
WebResponse createDirResp = webConversation.getResponse(createDirReq);
assertEquals(HttpURLConnection.HTTP_CREATED, createDirResp.getResponseCode());
}
private WebResponse createFileOnServer(String fileServletLocation, String filename, String fileContent) throws SAXException, IOException, JSONException {
webConversation.setExceptionsThrownOnErrorStatus(false);
WebRequest createFileReq = getPostFilesRequest(fileServletLocation, getNewFileJSON(filename).toString(), filename);
WebResponse createFileResp = webConversation.getResponse(createFileReq);
assertEquals(HttpURLConnection.HTTP_CREATED, createFileResp.getResponseCode());
createFileReq = getPutFileRequest(createFileResp.getHeaderField("Location"), fileContent);
createFileResp = webConversation.getResponse(createFileReq);
assertEquals(HttpURLConnection.HTTP_OK, createFileResp.getResponseCode());
return createFileResp;
}
private WebRequest createSetPreferenceRequest(String location, String key, String value) throws UnsupportedEncodingException {
String body = "key=" + URLEncoder.encode(key, "UTF-8") + "&value=" + URLEncoder.encode(value, "UTF-8");
return new PutMethodWebRequest(location, new ByteArrayInputStream(body.getBytes()), "application/x-www-form-urlencoded");
}
}