blob: 25c886569435136ce333cc7018bb7ad8228f08cd [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/origin_trials/OriginTrialContext.h"
#include "core/HTMLNames.h"
#include "core/dom/DOMException.h"
#include "core/dom/ExceptionCode.h"
#include "core/frame/FrameView.h"
#include "core/html/HTMLDocument.h"
#include "core/html/HTMLHeadElement.h"
#include "core/html/HTMLMetaElement.h"
#include "core/testing/DummyPageHolder.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "public/platform/WebTrialTokenValidator.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
namespace {
const char kNonExistingFeatureName[] = "This feature does not exist";
const char kFrobulateFeatureName[] = "Frobulate";
const char kFrobulateEnabledOrigin[] = "https://www.example.com";
const char kFrobulateEnabledOriginUnsecure[] = "http://www.example.com";
// Trial token which will appear valid
const char kGoodToken[] = "AnySignatureWillDo|https://www.example.com|Frobulate|2000000000";
class MockTokenValidator : public WebTrialTokenValidator {
public:
MockTokenValidator()
: m_response(false)
, m_callCount(0)
{
}
~MockTokenValidator() override {}
// blink::WebTrialTokenValidator implementation
bool validateToken(const blink::WebString& token, const blink::WebString& origin, const blink::WebString& featureName) override
{
m_callCount++;
return m_response;
}
// Useful methods for controlling the validator
void setResponse(bool response)
{
m_response = response;
}
void reset()
{
m_response = false;
m_callCount = 0;
}
int callCount()
{
return m_callCount;
}
private:
bool m_response;
int m_callCount;
DISALLOW_COPY_AND_ASSIGN(MockTokenValidator);
};
} // namespace
class OriginTrialContextTest : public ::testing::Test {
protected:
OriginTrialContextTest()
: m_page(DummyPageHolder::create())
, m_frameworkWasEnabled(RuntimeEnabledFeatures::experimentalFrameworkEnabled())
, m_tokenValidator(adoptPtr(new MockTokenValidator()))
{
if (!RuntimeEnabledFeatures::experimentalFrameworkEnabled()) {
RuntimeEnabledFeatures::setExperimentalFrameworkEnabled(true);
}
}
~OriginTrialContextTest()
{
if (!m_frameworkWasEnabled) {
RuntimeEnabledFeatures::setExperimentalFrameworkEnabled(false);
}
m_page.clear();
}
void SetUp() override
{
m_document = toHTMLDocument(&m_page->document());
setInnerHTML(
"<html>"
"<head>"
"</head>"
"<body>"
"</body>"
"</html>");
}
ExecutionContext* executionContext() { return &(m_page->document()); }
MockTokenValidator* tokenValidator() { return m_tokenValidator.get(); }
HTMLDocument& document() const { return *m_document; }
void setPageOrigin(const String& origin)
{
KURL pageURL(ParsedURLString, origin);
RefPtr<SecurityOrigin> pageOrigin = SecurityOrigin::create(pageURL);
m_page->document().updateSecurityOrigin(pageOrigin);
}
void setInnerHTML(const char* htmlContent)
{
document().documentElement()->setInnerHTML(String::fromUTF8(htmlContent), ASSERT_NO_EXCEPTION);
document().view()->updateAllLifecyclePhases();
}
void addTrialToken(const String& token)
{
HTMLElement* head = document().head();
ASSERT_TRUE(head);
RefPtrWillBeRawPtr<HTMLMetaElement> meta = HTMLMetaElement::create(document());
meta->setAttribute(HTMLNames::nameAttr, "origin-trials");
AtomicString value(token);
meta->setAttribute(HTMLNames::contentAttr, value);
head->appendChild(meta.release());
}
bool isFeatureEnabled(const String& origin, const String& featureName, const String& token, String* errorMessage)
{
setPageOrigin(origin);
addTrialToken(token);
return OriginTrialContext::isFeatureEnabled(executionContext(), featureName, errorMessage, tokenValidator());
}
bool isFeatureEnabledWithoutErrorMessage(const String& origin, const String& featureName, const char* token)
{
return isFeatureEnabled(origin, featureName, token, nullptr);
}
private:
OwnPtr<DummyPageHolder> m_page;
RefPtrWillBePersistent<HTMLDocument> m_document;
const bool m_frameworkWasEnabled;
OwnPtr<MockTokenValidator> m_tokenValidator;
};
TEST_F(OriginTrialContextTest, EnabledNonExistingFeature)
{
String errorMessage;
bool isNonExistingFeatureEnabled = isFeatureEnabled(kFrobulateEnabledOrigin,
kNonExistingFeatureName,
kGoodToken,
&errorMessage);
EXPECT_FALSE(isNonExistingFeatureEnabled);
EXPECT_EQ(("The provided token(s) are not valid for the 'This feature does not exist' feature."), errorMessage);
}
TEST_F(OriginTrialContextTest, EnabledNonExistingFeatureWithoutErrorMessage)
{
bool isNonExistingFeatureEnabled = isFeatureEnabledWithoutErrorMessage(
kFrobulateEnabledOrigin,
kNonExistingFeatureName,
kGoodToken);
EXPECT_FALSE(isNonExistingFeatureEnabled);
}
// The feature should be enabled if a valid token for the origin is provided
TEST_F(OriginTrialContextTest, EnabledSecureRegisteredOrigin)
{
String errorMessage;
tokenValidator()->setResponse(true);
bool isOriginEnabled = isFeatureEnabled(kFrobulateEnabledOrigin,
kFrobulateFeatureName,
kGoodToken,
&errorMessage);
EXPECT_TRUE(isOriginEnabled);
EXPECT_TRUE(errorMessage.isEmpty()) << "Message should be empty, was: " << errorMessage;
EXPECT_EQ(1, tokenValidator()->callCount());
}
// ... but if the browser says it's invalid for any reason, that's enough to
// reject.
TEST_F(OriginTrialContextTest, InvalidTokenResponseFromPlatform)
{
String errorMessage;
tokenValidator()->setResponse(false);
bool isOriginEnabled = isFeatureEnabled(kFrobulateEnabledOrigin,
kFrobulateFeatureName,
kGoodToken,
&errorMessage);
EXPECT_FALSE(isOriginEnabled);
EXPECT_EQ(("The provided token(s) are not valid for the 'Frobulate' feature."), errorMessage);
EXPECT_EQ(1, tokenValidator()->callCount());
}
TEST_F(OriginTrialContextTest, EnabledSecureRegisteredOriginWithoutErrorMessage)
{
tokenValidator()->setResponse(true);
bool isOriginEnabled = isFeatureEnabledWithoutErrorMessage(
kFrobulateEnabledOrigin,
kFrobulateFeatureName,
kGoodToken);
EXPECT_TRUE(isOriginEnabled);
EXPECT_EQ(1, tokenValidator()->callCount());
}
// The feature should not be enabled if the origin is unsecure, even if a valid
// token for the origin is provided
TEST_F(OriginTrialContextTest, EnabledNonSecureRegisteredOrigin)
{
String errorMessage;
bool isOriginEnabled = isFeatureEnabled(kFrobulateEnabledOriginUnsecure,
kFrobulateFeatureName,
kGoodToken,
&errorMessage);
EXPECT_FALSE(isOriginEnabled);
EXPECT_TRUE(errorMessage.contains("secure origin")) << "Message should indicate only secure origins are allowed, was: " << errorMessage;
EXPECT_EQ(0, tokenValidator()->callCount());
}
} // namespace blink