org.auraframework.integration.test.http.AuraFrameworkServletHttpTest.java Source code

Java tutorial

Introduction

Here is the source code for org.auraframework.integration.test.http.AuraFrameworkServletHttpTest.java

Source

/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.auraframework.integration.test.http;

import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.auraframework.adapter.ConfigAdapter;
import org.auraframework.http.AuraBaseServlet;
import org.auraframework.integration.test.util.AuraHttpTestCase;
import org.auraframework.system.AuraContext.Authentication;
import org.auraframework.system.AuraContext.Format;
import org.auraframework.system.AuraContext.Mode;
import org.auraframework.util.test.annotation.ThreadHostileTest;
import org.auraframework.util.test.annotation.UnAdaptableTest;
import org.junit.Test;

/**
 * Automation to verify the implementation of AuraFrameworkServlet. AuraFrameworkServlet responds to requests of pattern
 * /auraFW/* This config is stored in aura/dist/config/web.xml for aura running on jetty. In SFDC build, the config is
 * in main-sfdc/config/aura.conf AuraFrameworkServlet sets resources to be cached for 45 days.
 */
public class AuraFrameworkServletHttpTest extends AuraHttpTestCase {
    public final String sampleBinaryResourcePath = "/auraFW/resources/aura/auraIdeLogo.png";
    public final String sampleTextResourcePath = "/auraFW/resources/aura/resetCSS.css";
    public final String sampleJavascriptResourcePath = "/auraFW/javascript/aura_dev.js";
    public final String sampleJavascriptResourcePathWithNonce = "/auraFW/javascript/%s/aura_dev.js";
    public final String sampleBinaryResourcePathWithNonce = "/auraFW/resources/%s/aura/auraIdeLogo.png";
    public final String sampleTextResourcePathWithNonce = "/auraFW/resources/%s/aura/resetCSS.css";
    private final long timeWindowExpiry = 600000; // ten minute expiration test window

    @Inject
    private ConfigAdapter configAdapter;

    /**
     * Starts a context with DEV mode.
     */
    @Override
    protected void startDefaultContext() {
        contextService.startContext(Mode.DEV, Format.JSON, Authentication.AUTHENTICATED);
    }

    private boolean ApproximatelyEqual(long a, long b, long delta) {
        return (Math.abs(a - b) < delta);
    }

    private SimpleDateFormat getHttpDateFormat() {
        return new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
    }

    /**
     * Execute a get method and check that we got a long cache response.
     */
    private int checkLongCache(HttpResponse httpResponse, String mimeType) throws Exception {

        int statusCode = getStatusCode(httpResponse);
        assertEquals("AuraFrameworkServlet failed to fetch a valid resource request.", HttpStatus.SC_OK,
                statusCode);
        assertNotNull(getResponseBody(httpResponse));

        String charset = getCharset(httpResponse);
        String responseMime = httpResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();

        if (mimeType.startsWith("text/")) {

            assertEquals("Framework servlet not responding with correct encoding type.",
                    AuraBaseServlet.UTF_ENCODING, charset);
            assertTrue("Framework servlet not responding with correct mime type expected " + mimeType + " got "
                    + responseMime, responseMime.startsWith(mimeType + ";"));
        } else {
            assertEquals("Framework servlet not responding with correct mime type", mimeType, responseMime);
        }

        SimpleDateFormat df = getHttpDateFormat();
        Date currentDate = new Date();
        long expirationMillis = (df.parse(httpResponse.getFirstHeader(HttpHeaders.EXPIRES).getValue()).getTime()
                - currentDate.getTime());
        assertTrue("AuraFrameworkServlet is not setting the right value for expires header.",
                ApproximatelyEqual(expirationMillis, AuraBaseServlet.LONG_EXPIRE, timeWindowExpiry));
        return statusCode;
    }

    private int checkExpired(HttpResponse response, String mimeType) throws Exception {
        int statusCode = getStatusCode(response);
        SimpleDateFormat df = getHttpDateFormat();
        assertEquals("AuraFrameworkServlet failed to return ok.", HttpStatus.SC_OK, statusCode);

        String charset = getCharset(response);
        String responseMime = response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();

        if (mimeType.startsWith("text/")) {

            assertEquals("Framework servlet not responding with correct encoding type.",
                    AuraBaseServlet.UTF_ENCODING, charset);
            assertTrue("Framework servlet not responding with correct mime type expected " + mimeType + " got "
                    + responseMime, responseMime.startsWith(mimeType + ";"));
        } else {
            assertEquals("Framework servlet not responding with correct mime type", mimeType, responseMime);
        }
        long expirationMillis = df.parse(response.getFirstHeader(HttpHeaders.EXPIRES).getValue()).getTime();
        assertTrue("AuraFrameworkServlet is not setting the right value for expires header.",
                expirationMillis < System.currentTimeMillis());
        EntityUtils.consume(response.getEntity());
        return statusCode;
    }

    protected HttpGet obtainNoncedGetMethod(String noncedPath, boolean fake) throws Exception {
        String nonce;

        if (fake) {
            nonce = "thisisnotanonce";
        } else {
            nonce = configAdapter.getAuraFrameworkNonce();
        }
        String realPath = String.format(noncedPath, nonce);

        return obtainGetMethod(realPath);
    }

    /**
     * Verify that AuraFrameworkServlet can handle bad resource paths. 1. Non existing resource path. 2. Empty resource
     * path. 3. Access to root directory or directory walking.
     */
    @Test
    public void testBadResourcePaths() throws Exception {
        String[] badUrls = { "/auraFW", "/auraFW/", "/auraFW/root/",
                // BUG "/auraFW/resources/aura/..", Causes a 500
                "/auraFW/resources/aura/../../",
                // BUG "/auraFW/resources/aura/../../../../", causes a 400
                "/auraFW/home/", "/auraFW/resources/aura/home", "/auraFW/resources/foo/bar",
                // Make sure the regex used in implementation doesn't barf
                "/auraFW/resources/aura/resources/aura/auraIdeLogo.png",
                "/auraFW/resources/aura/auraIdeLogo.png/resources/aura/" };
        for (String url : badUrls) {
            HttpGet get = obtainGetMethod(url);
            int statusCode = getStatusCode(perform(get));
            get.releaseConnection();
            assertEquals("Expected:" + HttpStatus.SC_NOT_FOUND + " but found " + statusCode
                    + ", when trying to reach:" + url, HttpStatus.SC_NOT_FOUND, statusCode);
        }
    }

    private void verifyResourceAccess(String resourcePath, int expectedResponseStatus, String failureMsg)
            throws Exception {
        HttpGet get = obtainGetMethod(resourcePath);
        HttpResponse response = perform(get);
        int statusCode = getStatusCode(response);
        get.releaseConnection();
        assertEquals(failureMsg, expectedResponseStatus, statusCode);

    }

    /**
     * Verify that incomplete resource path returns SC_NOT_FOUND(404). Subsequent requests for valid resource on the
     * same path are successful.
     *
     * @throws Exception
     */
    @Test
    public void testRequestingFolderAsFileNotAllowed() throws Exception {
        String[] parts = sampleBinaryResourcePath.split("/");
        // Accessing folder(which might have had previous valid access) as file
        String incompletePath = StringUtils.join(Arrays.copyOfRange(parts, 0, parts.length - 1), "/");
        verifyResourceAccess(incompletePath, HttpStatus.SC_NOT_FOUND,
                "Expected server to return a 404 status for folder as file.");

        // Accessing a valid folder
        verifyResourceAccess(incompletePath + "/", HttpStatus.SC_NOT_FOUND,
                "Expected server to return a 404 status for folders(incomplete paths).");

        // Subsequent requests for filed on same path are accepted and serviced
        verifyResourceAccess(sampleBinaryResourcePath, HttpStatus.SC_OK,
                "Expected server to return a 200 status for valid resource.");

    }

    /**
     * Test that AuraFrameworkServlet inspects the date header in the request and sends 200 even though
     * If-Modified-Since header indicates that resource is not stale, but has no fwUid or nonce
     */
    @Test
    public void testResourceCachingWithoutUidNonce() throws Exception {
        Calendar stamp = Calendar.getInstance();
        stamp.add(Calendar.DAY_OF_YEAR, 45);

        Header[] headers = new Header[] { new BasicHeader(HttpHeaders.IF_MODIFIED_SINCE,
                new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(stamp.getTime())) };

        HttpGet get = obtainGetMethod(sampleBinaryResourcePath, false, headers);
        HttpResponse httpResponse = perform(get);
        int statusCode = getStatusCode(httpResponse);
        String response = getResponseBody(httpResponse);
        get.releaseConnection();
        assertEquals("Expected server to return a 200 for unexpired cache without fwUid or nonce.",
                HttpStatus.SC_OK, statusCode);
        assertNotNull(response);
        assertDefaultAntiClickjacking(httpResponse, true, false);
    }

    /**
     * Test that AuraFrameworkServlet inspects the date header in the request and sends 304(SC_NOT_MODIFIED) if the
     * If-Modified-Since header indicates that resource is not stale.
     */
    @Test
    public void testResourceCachingWithUid() throws Exception {

        Calendar stamp = Calendar.getInstance();
        stamp.add(Calendar.DAY_OF_YEAR, 45);

        HttpGet get = obtainNoncedGetMethod(sampleBinaryResourcePathWithNonce, false);
        get.setHeader(HttpHeaders.IF_MODIFIED_SINCE,
                new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").format(stamp.getTime()));

        HttpResponse httpResponse = perform(get);

        int statusCode = getStatusCode(httpResponse);
        assertEquals("Expected server to return a 304 for unexpired cache.", HttpStatus.SC_NOT_MODIFIED,
                statusCode);
        assertNull(getResponseBody(httpResponse));
        assertDefaultAntiClickjacking(httpResponse, true, false);
    }

    /**
     * Verify the Vary header is set to Accept-Encoding. This should be set for cacheable and compressed js/css files.
     *
     * UnAdaptableTest because SFDC removes vary header
     */
    @UnAdaptableTest
    @Test
    public void testHasVaryHeader() throws Exception {
        HttpGet get = obtainNoncedGetMethod(sampleTextResourcePath, false);
        HttpResponse response = perform(get);
        Header varyHeader = response.getFirstHeader(HttpHeaders.VARY);
        assertNotNull("Vary header is not set.", varyHeader);
        assertEquals("Vary header set to wrong value.", "Accept-Encoding", varyHeader.getValue());
        assertDefaultAntiClickjacking(response, true, false);
    }

    /**
     * Verify that AuraFrameworkServlet responds successfully to valid request for a binary resource.
     */
    @Test
    public void testRequestBinaryResourceWithNonce() throws Exception {
        HttpGet get = obtainNoncedGetMethod(sampleBinaryResourcePathWithNonce, false);
        SimpleDateFormat df = getHttpDateFormat();
        HttpResponse response = perform(get);
        int statusCode;
        checkLongCache(response, "image/png");
        get.releaseConnection();

        get = obtainNoncedGetMethod(sampleBinaryResourcePathWithNonce, false);
        // set the if modified since to a long time ago.
        get.setHeader(HttpHeaders.IF_MODIFIED_SINCE, df.format(new Date(1)));
        statusCode = getStatusCode(perform(get));
        EntityUtils.consume(response.getEntity());
        get.releaseConnection();
        assertEquals("AuraFrameworkServlet failed to return not modified.", HttpStatus.SC_NOT_MODIFIED, statusCode);

        get = obtainNoncedGetMethod(sampleBinaryResourcePathWithNonce, false);
        // set the if modified since to the future.
        get.setHeader(HttpHeaders.IF_MODIFIED_SINCE,
                df.format(new Date(System.currentTimeMillis() + 24 * 3600 * 1000)));
        statusCode = getStatusCode(perform(get));
        EntityUtils.consume(response.getEntity());
        get.releaseConnection();
        assertEquals("AuraFrameworkServlet failed to return not modified.", HttpStatus.SC_NOT_MODIFIED, statusCode);

        get = obtainNoncedGetMethod(sampleBinaryResourcePathWithNonce, true);
        response = perform(get);

        checkExpired(response, "image/png");
        assertDefaultAntiClickjacking(response, true, false);
        get.releaseConnection();
    }

    /**
     * Verify that AuraFrameworkServlet responds successfully to valid request for a binary resource.
     */
    @Test
    public void testRequestBinaryResourceShortExpire() throws Exception {
        HttpGet get = obtainGetMethod(sampleBinaryResourcePath, false);
        HttpResponse httpResponse = perform(get);
        int statusCode = getStatusCode(httpResponse);
        String response = getResponseBody(httpResponse);
        String contentType = httpResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();
        String expires = httpResponse.getFirstHeader(HttpHeaders.EXPIRES).getValue();
        get.releaseConnection();

        assertEquals("AuraFrameworkServlet failed to fetch a valid resource request.", HttpStatus.SC_OK,
                statusCode);
        assertNotNull(response);
        assertEquals("Framework servlet not responding with correct mime type", "image/png", contentType);
        SimpleDateFormat df = getHttpDateFormat();
        Date currentDate = new Date();
        long expirationMillis = (df.parse(expires).getTime() - currentDate.getTime());
        assertTrue("AuraFrameworkServlet is not setting the right value for expires header.",
                ApproximatelyEqual(expirationMillis, AuraBaseServlet.SHORT_EXPIRE, timeWindowExpiry));
        assertDefaultAntiClickjacking(httpResponse, true, false);
    }

    /**
     * Verify that AuraFrameworkServlet responds successfully to valid request for a text resource.
     */
    @Test
    public void testRequestTextResourceWithNonce() throws Exception {
        HttpGet get = obtainNoncedGetMethod(sampleTextResourcePathWithNonce, false);
        HttpResponse response = perform(get);
        SimpleDateFormat df = getHttpDateFormat();
        checkLongCache(response, "text/css");
        get.releaseConnection();

        get = obtainNoncedGetMethod(sampleTextResourcePathWithNonce, false);
        // set the if modified since to a long time ago.
        get.setHeader(HttpHeaders.IF_MODIFIED_SINCE, df.format(new Date(1)));
        int statusCode = getStatusCode(perform(get));
        get.releaseConnection();
        assertEquals("AuraFrameworkServlet failed to return not modified.", HttpStatus.SC_NOT_MODIFIED, statusCode);

        get = obtainNoncedGetMethod(sampleTextResourcePathWithNonce, true);
        response = perform(get);
        checkExpired(response, "text/css");
        assertDefaultAntiClickjacking(response, true, false);
        get.releaseConnection();
    }

    /**
     * Verify that AuraFrameworkServlet responds successfully to valid request for a text resource.
     */
    @Test
    public void testRequestTextResourceShortExpire() throws Exception {
        HttpGet get = obtainGetMethod(sampleTextResourcePath);
        HttpResponse httpResponse = perform(get);
        int statusCode = getStatusCode(httpResponse);
        String response = getResponseBody(httpResponse);
        String charset = getCharset(httpResponse);
        get.releaseConnection();

        assertEquals("AuraFrameworkServlet failed to fetch a valid resource request.", HttpStatus.SC_OK,
                statusCode);
        assertNotNull(response);

        assertEquals("Framework servlet not responding with correct encoding type.", AuraBaseServlet.UTF_ENCODING,
                charset);
        assertTrue("Framework servlet not responding with correct mime type",
                httpResponse.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue().startsWith("text/css;"));
        SimpleDateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
        Date currentDate = new Date();
        long expirationMillis = (df.parse(httpResponse.getFirstHeader(HttpHeaders.EXPIRES).getValue()).getTime()
                - currentDate.getTime());
        assertTrue("AuraFrameworkServlet is not setting the right value for expires header.",
                ApproximatelyEqual(expirationMillis, AuraBaseServlet.SHORT_EXPIRE, timeWindowExpiry));
        assertDefaultAntiClickjacking(httpResponse, true, false);
    }

    /**
     * Verify that AuraFrameworkServlet responds successfully to valid request for a javascript resource.
     */
    @Test
    public void testRequestJavascriptResourceNoExpire() throws Exception {
        HttpGet get = obtainGetMethod(sampleJavascriptResourcePath);
        HttpResponse response = perform(get);

        checkExpired(response, "text/javascript");
        assertDefaultAntiClickjacking(response, true, false);
        get.releaseConnection();
    }

    /**
     * Verify that AuraFrameworkServlet responds successfully to valid request for nonced aura js
     */
    @Test
    public void testRequestJavascriptResourceLongExpire() throws Exception {
        HttpGet get = obtainNoncedGetMethod(sampleJavascriptResourcePathWithNonce, false);
        HttpResponse response = perform(get);
        checkLongCache(response, "text/javascript");
        assertDefaultAntiClickjacking(response, true, false);
        get.releaseConnection();
    }

    @ThreadHostileTest("PRODUCTION")
    @Test
    public void testExistingMinifiedResource() throws Exception {
        getMockConfigAdapter().setIsProduction(true);

        HttpGet get = obtainGetMethod("/auraFW/resources/moment/moment.js");
        HttpResponse httpResponse = perform(get);
        String response = getResponseBody(httpResponse);
        checkExpired(httpResponse, "text/javascript");
        assertDefaultAntiClickjacking(httpResponse, true, false);
        assertTrue(response.contains("!function(a,b){"));

        get.releaseConnection();
    }

    @ThreadHostileTest("PRODUCTION")
    @Test
    public void testNonExistentMinifiedResource() throws Exception {
        getMockConfigAdapter().setIsProduction(true);

        HttpGet get = obtainGetMethod("/auraFW/resources/codemirror/lib/codemirror.js");
        HttpResponse httpResponse = perform(get);
        String response = getResponseBody(httpResponse);

        checkExpired(httpResponse, "text/javascript");
        assertTrue(response.contains("function CodeMirror(place, options)"));
        assertDefaultAntiClickjacking(httpResponse, true, false);

        get.releaseConnection();
    }

    @ThreadHostileTest("PRODUCTION")
    @Test
    public void testResourceNameWithoutPeriod() throws Exception {
        getMockConfigAdapter().setIsProduction(true);

        HttpGet get = obtainGetMethod("/auraFW/resources/nosuffix");
        HttpResponse httpResponse = perform(get);
        String response = getResponseBody(httpResponse);

        checkExpired(httpResponse, "text/javascript");
        assertTrue(response.equals("this file has no suffix"));
        assertDefaultAntiClickjacking(httpResponse, true, false);

        get.releaseConnection();
    }
}