blob: 843129ea6e19430f67f023d30eb036d603f0edd3 [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 "core/testing/NullExecutionContext.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "public/platform/WebTrialTokenValidator.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "wtf/Vector.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::WebSecurityOrigin& 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);
};
// Concrete subclass of OriginTrialContext which simply maintains a vector of
// token strings to use for tests.
class TestOriginTrialContext : public OriginTrialContext {
public:
TestOriginTrialContext()
: m_parent(new NullExecutionContext())
{
}
~TestOriginTrialContext() override = default;
ExecutionContext* getExecutionContext() override { return m_parent.get(); }
void addToken(const String& token)
{
m_tokens.append(token);
}
void updateSecurityOrigin(const String& origin)
{
KURL pageURL(ParsedURLString, origin);
RefPtr<SecurityOrigin> pageOrigin = SecurityOrigin::create(pageURL);
m_parent->setSecurityOrigin(pageOrigin);
m_parent->setIsSecureContext(SecurityOrigin::isSecure(pageURL));
}
Vector<String> getTokens() override
{
Vector<String> tokens;
for (String token : m_tokens) {
tokens.append(token);
}
return tokens;
}
DEFINE_INLINE_VIRTUAL_TRACE()
{
visitor->trace(m_parent);
OriginTrialContext::trace(visitor);
}
private:
Member<NullExecutionContext> m_parent;
Vector<String> m_tokens;
};
} // namespace
class OriginTrialContextTest : public ::testing::Test {
protected:
OriginTrialContextTest()
: m_frameworkWasEnabled(RuntimeEnabledFeatures::experimentalFrameworkEnabled())
, m_tokenValidator(adoptPtr(new MockTokenValidator()))
, m_originTrialContext(new TestOriginTrialContext)
{
RuntimeEnabledFeatures::setExperimentalFrameworkEnabled(true);
}
~OriginTrialContextTest()
{
RuntimeEnabledFeatures::setExperimentalFrameworkEnabled(m_frameworkWasEnabled);
}
MockTokenValidator* tokenValidator() { return m_tokenValidator.get(); }
bool isFeatureEnabled(const String& origin, const String& featureName, const String& token, String* errorMessage)
{
m_originTrialContext->updateSecurityOrigin(origin);
m_originTrialContext->addToken(token);
return m_originTrialContext->isFeatureEnabled(featureName, errorMessage, tokenValidator());
}
bool isFeatureEnabledWithoutErrorMessage(const String& origin, const String& featureName, const char* token)
{
return isFeatureEnabled(origin, featureName, token, nullptr);
}
private:
const bool m_frameworkWasEnabled;
OwnPtr<MockTokenValidator> m_tokenValidator;
Persistent<TestOriginTrialContext> m_originTrialContext;
};
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, OnlyOneErrorMessageGenerated)
{
String errorMessage1;
String errorMessage2;
tokenValidator()->setResponse(false);
isFeatureEnabled(kFrobulateEnabledOrigin, kFrobulateFeatureName, kGoodToken, &errorMessage1);
isFeatureEnabled(kFrobulateEnabledOrigin, kFrobulateFeatureName, kGoodToken, &errorMessage2);
EXPECT_FALSE(errorMessage1.isEmpty());
EXPECT_TRUE(errorMessage2.isEmpty());
}
TEST_F(OriginTrialContextTest, ErrorMessageClearedIfStringReused)
{
String errorMessage;
tokenValidator()->setResponse(false);
isFeatureEnabled(kFrobulateEnabledOrigin, kFrobulateFeatureName, kGoodToken, &errorMessage);
EXPECT_FALSE(errorMessage.isEmpty());
isFeatureEnabled(kFrobulateEnabledOrigin, kFrobulateFeatureName, kGoodToken, &errorMessage);
EXPECT_TRUE(errorMessage.isEmpty());
}
TEST_F(OriginTrialContextTest, ErrorMessageGeneratedPerFeature)
{
String errorMessage1;
String errorMessage2;
tokenValidator()->setResponse(false);
isFeatureEnabled(kFrobulateEnabledOrigin, kFrobulateFeatureName, kGoodToken, &errorMessage1);
isFeatureEnabled(kFrobulateEnabledOrigin, kNonExistingFeatureName, kGoodToken, &errorMessage2);
EXPECT_FALSE(errorMessage1.isEmpty());
EXPECT_FALSE(errorMessage2.isEmpty());
}
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_EQ(0, tokenValidator()->callCount());
EXPECT_FALSE(errorMessage.isEmpty());
}
TEST_F(OriginTrialContextTest, EnabledNonSecureRegisteredOriginWithoutErrorMessage)
{
bool isOriginEnabled = isFeatureEnabledWithoutErrorMessage(
kFrobulateEnabledOriginUnsecure,
kFrobulateFeatureName,
kGoodToken);
EXPECT_FALSE(isOriginEnabled);
EXPECT_EQ(0, tokenValidator()->callCount());
}
} // namespace blink