Skip to content
This repository has been archived by the owner on Apr 3, 2020. It is now read-only.

Commit

Permalink
Add Suborigin header and access control check for XHR
Browse files Browse the repository at this point in the history
This adds the plumbing for adding the Suborigin header on requests, and
it particularly adds it for XHR preflights. This also adds tests to
verify the behavior for XHR.

BUG=336894

Review URL: https://codereview.chromium.org/1435123002

Cr-Commit-Position: refs/heads/master@{#360220}
  • Loading branch information
joelweinberger authored and Commit bot committed Nov 18, 2015
1 parent ffa5a6c commit 6ba78c3
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
if (strtolower($_GET["credentials"]) == "true") {
header("Access-Control-Allow-Credentials: true");
}

if ($_SERVER["HTTP_SUBORIGIN"] == "foobar") {
header("Access-Control-Allow-Suborigin: foobar");
}

header("Content-Type: application/javascript");
$delay = $_GET['delay'];
if ($delay)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CONSOLE MESSAGE: line 9: If a Suborigin makes a request, a response without an Access-Control-Allow-Suborigin header should fail and should output a reasonable error message.
CONSOLE ERROR: XMLHttpRequest cannot load http://127.0.0.1:8000/security/resources/cors-script.php?cors=false. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://foobar_127.0.0.1:8000' is therefore not allowed access.
ALERT: PASS: XHR correctly failed

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
header("Content-Security-Policy: suborigin foobar");
?>
<!DOCTYPE html>
<html>
<body>
<script>
if (window.testRunner) {
testRunner.waitUntilDone();
testRunner.dumpAsText();
}
console.log("If a Suborigin makes a request, a response without an Access-Control-Allow-Suborigin header should fail and should output a reasonable error message.");

function success() {
alert("PASS: XHR correctly failed");
if (window.testRunner)
testRunner.notifyDone();
}

function failure() {
alert("FAIL: XHR incorrectly succeeded");
if (window.testRunner)
testRunner.notifyDone();
}

var xhr = new XMLHttpRequest();
xhr.onerror = success;
xhr.onload = failure;
xhr.open("GET", "http://127.0.0.1:8000/security/resources/cors-script.php?cors=false");
xhr.send();
</script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php
header("Content-Security-Policy: suborigin foobar");
?>
<!DOCTYPE html>
<html>
<head>
<title>Allow suborigin in HTTP header</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/security/suborigins/resources/suborigin-cors-lib.js"></script>
</head>
<body>
<div id="container"></div>
<script>
// XMLHttpRequest tests
var SuboriginXHRTest = function(pass, name, src, crossoriginValue) {
SuboriginTest.call(this, pass, "XHR: " + name, src, crossoriginValue);
}

SuboriginXHRTest.prototype.execute = function() {
var test = async_test(this.name);
var xhr = new XMLHttpRequest();

if (this.crossoriginValue === 'use-credentials') {
xhr.withCredentials = true;
}

if (this.pass) {
xhr.onload = function() {
test.done();
};
xhr.onerror = function() {
test.step(function() { assert_unreached("Good XHR fired error handler."); });
};
} else {
xhr.onload = function() {
test.step(function() { assert_unreached("Bad XHR successful."); });
};
xhr.onerror = function() {
test.done();
};
}

xhr.open("GET", this.src);
xhr.send();
};

var xorigin_preflight_script = "http://127.0.0.1:8000/security/resources/cors-script.php";

// XHR preflight tests
new SuboriginXHRTest(
true,
"Basic anonymous XHR preflight",
xorigin_preflight_script + "?cors=http://foobar_127.0.0.1:8000",
"anonymous").execute();

new SuboriginXHRTest(
true,
"Basic anonymous XHR preflight with '*' ACAO",
xorigin_preflight_script + "?cors=*",
"anonymous").execute();

new SuboriginXHRTest(
true,
"Basic XHR with credentials preflight",
xorigin_preflight_script + "?cors=http://foobar_127.0.0.1:8000&credentials=true",
"use-credentials").execute();

new SuboriginXHRTest(
false,
"Basic XHR with credentials preflight with '*' ACAO",
xorigin_preflight_script + "?cors=*&credentials=true",
"use-credentials").execute();
</script>
</body>
</html>
17 changes: 15 additions & 2 deletions third_party/WebKit/Source/core/fetch/CrossOriginAccessControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ void updateRequestForAccessControl(ResourceRequest& request, SecurityOrigin* sec
request.setFetchCredentialsMode(allowCredentials == AllowStoredCredentials ? WebURLRequest::FetchCredentialsModeInclude : WebURLRequest::FetchCredentialsModeOmit);

if (securityOrigin)
request.setHTTPOrigin(securityOrigin->toAtomicString());
request.setHTTPOrigin(securityOrigin);
}

ResourceRequest createAccessControlPreflightRequest(const ResourceRequest& request, SecurityOrigin* securityOrigin)
Expand Down Expand Up @@ -133,6 +133,7 @@ bool passesAccessControlCheck(const ResourceResponse& response, StoredCredential
{
AtomicallyInitializedStaticReference(AtomicString, allowOriginHeaderName, (new AtomicString("access-control-allow-origin", AtomicString::ConstructFromLiteral)));
AtomicallyInitializedStaticReference(AtomicString, allowCredentialsHeaderName, (new AtomicString("access-control-allow-credentials", AtomicString::ConstructFromLiteral)));
AtomicallyInitializedStaticReference(AtomicString, allowSuboriginHeaderName, (new AtomicString("access-control-allow-suborigin", AtomicString::ConstructFromLiteral)));

int statusCode = response.httpStatusCode();

Expand All @@ -142,6 +143,18 @@ bool passesAccessControlCheck(const ResourceResponse& response, StoredCredential
}

const AtomicString& allowOriginHeaderValue = response.httpHeaderField(allowOriginHeaderName);

// Check Suborigins, unless the Access-Control-Allow-Origin is '*',
// which implies that all Suborigins are okay as well.
if (securityOrigin->hasSuborigin() && allowOriginHeaderValue != starAtom) {
const AtomicString& allowSuboriginHeaderValue = response.httpHeaderField(allowSuboriginHeaderName);
AtomicString atomicSuboriginName(securityOrigin->suboriginName());
if (allowSuboriginHeaderValue != starAtom && allowSuboriginHeaderValue != atomicSuboriginName) {
errorDescription = buildAccessControlFailureMessage("The 'Access-Control-Allow-Suborigin' header has a value '" + allowSuboriginHeaderValue + "' that is not equal to the supplied suborigin.", securityOrigin);
return false;
}
}

if (allowOriginHeaderValue == starAtom) {
// A wildcard Access-Control-Allow-Origin can not be used if credentials are to be sent,
// even with Access-Control-Allow-Credentials set to true.
Expand Down Expand Up @@ -271,7 +284,7 @@ bool CrossOriginAccessControl::handleRedirect(SecurityOrigin* securityOrigin, Re
if (redirectCrossOrigin) {
// If now to a different origin, update/set Origin:.
newRequest.clearHTTPOrigin();
newRequest.setHTTPOrigin(securityOrigin->toAtomicString());
newRequest.setHTTPOrigin(securityOrigin);
// If the user didn't request credentials in the first place, update our
// state so we neither request them nor expect they must be allowed.
if (options.credentialsRequested == ClientDidNotRequestCredentials)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ void DocumentThreadableLoader::loadActualRequest()
OwnPtr<ResourceLoaderOptions> actualOptions;
actualOptions.swap(m_actualOptions);

actualRequest->setHTTPOrigin(securityOrigin()->toAtomicString());
actualRequest->setHTTPOrigin(securityOrigin());

clearResource();

Expand Down
8 changes: 4 additions & 4 deletions third_party/WebKit/Source/core/loader/FrameFetchContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,16 @@ void FrameFetchContext::addAdditionalRequestHeaders(ResourceRequest& request, Fe
{
bool isMainResource = type == FetchMainResource;
if (!isMainResource) {
String outgoingOrigin;
RefPtr<SecurityOrigin> outgoingOrigin;
if (!request.didSetHTTPReferrer()) {
outgoingOrigin = m_document->outgoingOrigin();
outgoingOrigin = m_document->securityOrigin();
request.setHTTPReferrer(SecurityPolicy::generateReferrer(m_document->referrerPolicy(), request.url(), m_document->outgoingReferrer()));
} else {
RELEASE_ASSERT(SecurityPolicy::generateReferrer(request.referrerPolicy(), request.url(), request.httpReferrer()).referrer == request.httpReferrer());
outgoingOrigin = SecurityOrigin::createFromString(request.httpReferrer())->toString();
outgoingOrigin = SecurityOrigin::createFromString(request.httpReferrer());
}

request.addHTTPOriginIfNeeded(AtomicString(outgoingOrigin));
request.addHTTPOriginIfNeeded(outgoingOrigin);
}

if (m_document)
Expand Down
4 changes: 2 additions & 2 deletions third_party/WebKit/Source/core/loader/FrameLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ ResourceRequest FrameLoader::resourceRequestFromHistoryItem(HistoryItem* item,
request.setHTTPBody(formData);
request.setHTTPContentType(item->formContentType());
RefPtr<SecurityOrigin> securityOrigin = SecurityOrigin::createFromString(item->referrer().referrer);
request.addHTTPOriginIfNeeded(securityOrigin->toAtomicString());
request.addHTTPOriginIfNeeded(securityOrigin);
}
return request;
}
Expand Down Expand Up @@ -729,7 +729,7 @@ void FrameLoader::setReferrerForFrameRequest(ResourceRequest& request, ShouldSen

request.setHTTPReferrer(referrer);
RefPtr<SecurityOrigin> referrerOrigin = SecurityOrigin::createFromString(referrer.referrer);
request.addHTTPOriginIfNeeded(referrerOrigin->toAtomicString());
request.addHTTPOriginIfNeeded(referrerOrigin);
}

FrameLoadType FrameLoader::determineFrameLoadType(const FrameLoadRequest& request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ void XMLHttpRequest::createRequest(PassRefPtr<EncodedFormData> httpBody, Excepti
request.addHTTPHeaderFields(m_requestHeaders);

ThreadableLoaderOptions options;
options.preflightPolicy = uploadEvents ? ForcePreflight : ConsiderPreflight;
options.preflightPolicy = (uploadEvents || securityOrigin()->hasSuborigin()) ? ForcePreflight : ConsiderPreflight;
options.crossOriginRequestPolicy = UseAccessControl;
options.initiator = FetchInitiatorTypeNames::xmlhttprequest;
options.contentSecurityPolicyEnforcement = ContentSecurityPolicy::shouldBypassMainWorld(&executionContext) ? DoNotEnforceContentSecurityPolicy : EnforceConnectSrcDirective;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ WebReferrerPolicy WebURLRequest::referrerPolicy() const

void WebURLRequest::addHTTPOriginIfNeeded(const WebString& origin)
{
m_private->m_resourceRequest->addHTTPOriginIfNeeded(origin);
m_private->m_resourceRequest->addHTTPOriginIfNeeded(WebSecurityOrigin::createFromString(origin));
}

bool WebURLRequest::hasUserGesture() const
Expand Down
15 changes: 12 additions & 3 deletions third_party/WebKit/Source/platform/network/ResourceRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,20 @@ void ResourceRequest::clearHTTPReferrer()
m_didSetHTTPReferrer = false;
}

void ResourceRequest::setHTTPOrigin(PassRefPtr<SecurityOrigin> origin)
{
setHTTPHeaderField("Origin", origin->toAtomicString());
if (origin->hasSuborigin())
setHTTPHeaderField("Suborigin", AtomicString(origin->suboriginName()));
}

void ResourceRequest::clearHTTPOrigin()
{
m_httpHeaderFields.remove("Origin");
m_httpHeaderFields.remove("Suborigin");
}

void ResourceRequest::addHTTPOriginIfNeeded(const AtomicString& origin)
void ResourceRequest::addHTTPOriginIfNeeded(PassRefPtr<SecurityOrigin> origin)
{
if (!httpOrigin().isEmpty())
return; // Request already has an Origin header.
Expand All @@ -257,10 +265,11 @@ void ResourceRequest::addHTTPOriginIfNeeded(const AtomicString& origin)
// For non-GET and non-HEAD methods, always send an Origin header so the
// server knows we support this feature.

if (origin.isEmpty()) {
AtomicString originString = origin->toAtomicString();
if (originString.isEmpty()) {
// If we don't know what origin header to attach, we attach the value
// for an empty origin.
setHTTPOrigin(SecurityOrigin::createUnique()->toAtomicString());
setHTTPOrigin(SecurityOrigin::createUnique());
return;
}
setHTTPOrigin(origin);
Expand Down
7 changes: 5 additions & 2 deletions third_party/WebKit/Source/platform/network/ResourceRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,12 @@ class PLATFORM_EXPORT ResourceRequest {
void clearHTTPReferrer();

const AtomicString& httpOrigin() const { return httpHeaderField("Origin"); }
void setHTTPOrigin(const AtomicString& httpOrigin) { setHTTPHeaderField("Origin", httpOrigin); }
const AtomicString& httpSuborigin() const { return httpHeaderField("Suborigin"); }
// Note that these will also set and clear, respectively, the
// Suborigin header, if appropriate.
void setHTTPOrigin(PassRefPtr<SecurityOrigin>);
void clearHTTPOrigin();
void addHTTPOriginIfNeeded(const AtomicString& origin);
void addHTTPOriginIfNeeded(PassRefPtr<SecurityOrigin>);

const AtomicString& httpUserAgent() const { return httpHeaderField("User-Agent"); }
void setHTTPUserAgent(const AtomicString& httpUserAgent) { setHTTPHeaderField("User-Agent", httpUserAgent); }
Expand Down

0 comments on commit 6ba78c3

Please sign in to comment.