| // Copyright 2016 The Cobalt Authors. All Rights Reserved. |
| // |
| // 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. |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/strings/stringprintf.h" |
| #include "cobalt/base/polymorphic_downcast.h" |
| #include "cobalt/web/csp_delegate.h" |
| #include "cobalt/web/csp_delegate_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::HasSubstr; |
| using ::testing::SaveArg; |
| using ::testing::StrictMock; |
| using ::testing::ValuesIn; |
| |
| namespace cobalt { |
| namespace web { |
| |
| namespace { |
| |
| struct ResourcePair { |
| // Resource type queried for the test. |
| CspDelegate::ResourceType type; |
| // Directive to allow 'self' for. |
| const char* directive; |
| // Effective directive reported in the violation. |
| const char* effective_directive; |
| }; |
| |
| std::ostream& operator<<(std::ostream& out, const ResourcePair& obj) { |
| return out << obj.directive; |
| } |
| |
| const ResourcePair s_params[] = { |
| {CspDelegate::kFont, "font-src", "font-src"}, |
| {CspDelegate::kFont, "default-src", "font-src"}, |
| {CspDelegate::kImage, "img-src", "img-src"}, |
| {CspDelegate::kImage, "default-src", "img-src"}, |
| {CspDelegate::kLocation, "h5vcc-location-src", "h5vcc-location-src"}, |
| {CspDelegate::kMedia, "media-src", "media-src"}, |
| {CspDelegate::kMedia, "default-src", "media-src"}, |
| {CspDelegate::kScript, "script-src", "script-src"}, |
| {CspDelegate::kScript, "default-src", "script-src"}, |
| {CspDelegate::kStyle, "style-src", "style-src"}, |
| {CspDelegate::kStyle, "default-src", "style-src"}, |
| {CspDelegate::kWorker, "worker-src", "worker-src"}, |
| {CspDelegate::kWorker, "script-src", "worker-src"}, |
| {CspDelegate::kWorker, "default-src", "worker-src"}, |
| {CspDelegate::kXhr, "connect-src", "connect-src"}, |
| {CspDelegate::kXhr, "default-src", "connect-src"}, |
| {CspDelegate::kWebSocket, "connect-src", "connect-src"}, |
| {CspDelegate::kWebSocket, "default-src", "connect-src"}, |
| }; |
| |
| std::string ResourcePairName(::testing::TestParamInfo<ResourcePair> info) { |
| std::string directive(info.param.directive); |
| std::replace(directive.begin(), directive.end(), '-', '_'); |
| std::string effective_directive(info.param.effective_directive); |
| std::replace(effective_directive.begin(), effective_directive.end(), '-', |
| '_'); |
| return base::StringPrintf("type_%d_directive_%s_effective_%s", |
| info.param.type, directive.c_str(), |
| effective_directive.c_str()); |
| } |
| |
| class MockViolationReporter : public CspViolationReporter { |
| public: |
| MockViolationReporter() |
| : CspViolationReporter(NULL, network_bridge::PostSender()) {} |
| MOCK_METHOD1(Report, void(const csp::ViolationInfo&)); |
| }; |
| |
| class CspDelegateTest : public ::testing::TestWithParam<ResourcePair> { |
| protected: |
| virtual void SetUp(); |
| std::unique_ptr<CspDelegateSecure> csp_delegate_; |
| StrictMock<MockViolationReporter>* mock_reporter_; |
| }; |
| |
| // TODO: Combine this with the one in xml_http_request_test. |
| class ScopedLogInterceptor { |
| public: |
| explicit ScopedLogInterceptor(std::string* output) |
| : output_(output), old_handler_(logging::GetLogMessageHandler()) { |
| DCHECK(output_); |
| DCHECK(!log_interceptor_); |
| log_interceptor_ = this; |
| logging::SetLogMessageHandler(LogHandler); |
| } |
| |
| ~ScopedLogInterceptor() { |
| logging::SetLogMessageHandler(old_handler_); |
| log_interceptor_ = NULL; |
| } |
| |
| static bool LogHandler(int severity, const char* file, int line, |
| size_t message_start, const std::string& str) { |
| *log_interceptor_->output_ += str; |
| return true; |
| } |
| |
| private: |
| std::string* output_; |
| logging::LogMessageHandlerFunction old_handler_; |
| static ScopedLogInterceptor* log_interceptor_; |
| }; |
| |
| ScopedLogInterceptor* ScopedLogInterceptor::log_interceptor_; |
| |
| } // namespace |
| |
| void CspDelegateTest::SetUp() { |
| GURL origin("https://www.example.com"); |
| std::string default_navigation_policy("h5vcc-location-src 'self'"); |
| |
| mock_reporter_ = new StrictMock<MockViolationReporter>(); |
| std::unique_ptr<CspViolationReporter> reporter(mock_reporter_); |
| |
| csp_delegate_.reset(new CspDelegateSecure( |
| std::move(reporter), origin, csp::kCSPRequired, base::Closure())); |
| std::string policy; |
| if (!strcmp(GetParam().directive, "default-src")) { |
| policy = base::StringPrintf("%s 'self'", GetParam().directive); |
| } else { |
| policy = |
| base::StringPrintf("default-src none; %s 'self'", GetParam().directive); |
| } |
| csp_delegate_->OnReceiveHeader(policy, csp::kHeaderTypeEnforce, |
| csp::kHeaderSourceMeta); |
| } |
| |
| TEST_P(CspDelegateTest, LoadOk) { |
| CspDelegate::ResourceType param = GetParam().type; |
| GURL test_url("https://www.example.com"); |
| EXPECT_TRUE(csp_delegate_->CanLoad(param, test_url, false)); |
| } |
| |
| TEST_P(CspDelegateTest, LoadNotOk) { |
| CspDelegate::ResourceType param = GetParam().type; |
| std::string effective_directive = GetParam().effective_directive; |
| GURL test_url("http://www.evil.com"); |
| |
| csp::ViolationInfo info; |
| EXPECT_CALL(*mock_reporter_, Report(_)).WillOnce(SaveArg<0>(&info)); |
| EXPECT_FALSE(csp_delegate_->CanLoad(param, test_url, false)); |
| EXPECT_EQ(test_url, info.blocked_url); |
| EXPECT_EQ(effective_directive, info.effective_directive); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(CanLoad, CspDelegateTest, ValuesIn(s_params), |
| ResourcePairName); |
| |
| TEST(CspDelegateFactoryTest, Secure) { |
| std::unique_ptr<CspDelegate> delegate = |
| CspDelegateFactory::GetInstance()->Create( |
| kCspEnforcementEnable, std::unique_ptr<CspViolationReporter>(), |
| GURL(), csp::kCSPRequired, base::Closure()); |
| EXPECT_TRUE(delegate != NULL); |
| } |
| |
| TEST(CspDelegateFactoryTest, InsecureBlocked) { |
| std::string output; |
| { |
| // Capture the output, because we should get a FATAL log and we don't |
| // want to crash. |
| ScopedLogInterceptor li(&output); |
| std::unique_ptr<CspDelegate> delegate = |
| CspDelegateFactory::GetInstance()->Create( |
| kCspEnforcementDisable, std::unique_ptr<CspViolationReporter>(), |
| GURL(), csp::kCSPRequired, base::Closure()); |
| |
| std::unique_ptr<CspDelegate> empty_delegate; |
| EXPECT_EQ(empty_delegate.get(), delegate.get()); |
| } |
| EXPECT_THAT(output, HasSubstr("FATAL")); |
| } |
| |
| TEST(CspDelegateFactoryTest, InsecureAllowed) { |
| // This only compiles because this test is a friend of CspDelegateFactory, |
| // otherwise GetInsecureAllowedToken is private. |
| int token = CspDelegateFactory::GetInstance()->GetInsecureAllowedToken(); |
| std::unique_ptr<CspDelegate> delegate = |
| CspDelegateFactory::GetInstance()->Create( |
| kCspEnforcementDisable, std::unique_ptr<CspViolationReporter>(), |
| GURL(), csp::kCSPRequired, base::Closure(), token); |
| EXPECT_TRUE(delegate != NULL); |
| } |
| |
| } // namespace web |
| } // namespace cobalt |