sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include <iterator> |
| 6 | #include <string> |
waffles | 77255cc | 2016-08-02 17:25:12 | [diff] [blame] | 7 | #include <utility> |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 8 | #include <vector> |
| 9 | |
| 10 | #include "base/callback.h" |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 11 | #include "base/files/file_path.h" |
| 12 | #include "base/files/file_util.h" |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 13 | #include "base/macros.h" |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 14 | #include "base/memory/ptr_util.h" |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 15 | #include "base/memory/ref_counted.h" |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 16 | #include "base/path_service.h" |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 17 | #include "base/run_loop.h" |
fdoray | b26583c | 2017-05-16 18:47:01 | [diff] [blame] | 18 | #include "base/task_scheduler/post_task.h" |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 19 | #include "base/test/scoped_path_override.h" |
fdoray | b26583c | 2017-05-16 18:47:01 | [diff] [blame] | 20 | #include "base/test/scoped_task_environment.h" |
| 21 | #include "base/threading/thread_task_runner_handle.h" |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 22 | #include "base/version.h" |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 23 | #include "components/component_updater/component_updater_paths.h" |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 24 | #include "components/component_updater/component_updater_service.h" |
| 25 | #include "components/component_updater/component_updater_service_internal.h" |
| 26 | #include "components/component_updater/default_component_installer.h" |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 27 | #include "components/update_client/component_unpacker.h" |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 28 | #include "components/update_client/crx_update_item.h" |
| 29 | #include "components/update_client/test_configurator.h" |
| 30 | #include "components/update_client/update_client.h" |
sorin | 7b865052 | 2016-11-02 18:23:41 | [diff] [blame] | 31 | #include "components/update_client/update_client_errors.h" |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 32 | #include "testing/gmock/include/gmock/gmock.h" |
| 33 | #include "testing/gtest/include/gtest/gtest.h" |
| 34 | |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 35 | using ComponentUnpacker = update_client::ComponentUnpacker; |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 36 | using Configurator = update_client::Configurator; |
| 37 | using CrxUpdateItem = update_client::CrxUpdateItem; |
| 38 | using TestConfigurator = update_client::TestConfigurator; |
| 39 | using UpdateClient = update_client::UpdateClient; |
| 40 | |
| 41 | using ::testing::_; |
| 42 | using ::testing::Invoke; |
| 43 | |
| 44 | namespace component_updater { |
| 45 | |
| 46 | namespace { |
| 47 | |
| 48 | // This hash corresponds to jebgalgnebhfojomionfpkfelancnnkf.crx. |
| 49 | const uint8_t kSha256Hash[] = {0x94, 0x16, 0x0b, 0x6d, 0x41, 0x75, 0xe9, 0xec, |
| 50 | 0x8e, 0xd5, 0xfa, 0x54, 0xb0, 0xd2, 0xdd, 0xa5, |
| 51 | 0x6e, 0x05, 0x6b, 0xe8, 0x73, 0x47, 0xf6, 0xc4, |
| 52 | 0x11, 0x9f, 0xbc, 0xb3, 0x09, 0xb3, 0x5b, 0x40}; |
| 53 | |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 54 | constexpr base::FilePath::CharType relative_install_dir[] = |
| 55 | FILE_PATH_LITERAL("fake"); |
| 56 | |
| 57 | base::FilePath test_file(const char* file) { |
| 58 | base::FilePath path; |
| 59 | PathService::Get(base::DIR_SOURCE_ROOT, &path); |
| 60 | return path.AppendASCII("components") |
| 61 | .AppendASCII("test") |
| 62 | .AppendASCII("data") |
| 63 | .AppendASCII("update_client") |
| 64 | .AppendASCII(file); |
| 65 | } |
| 66 | |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 67 | class MockUpdateClient : public UpdateClient { |
| 68 | public: |
sorin | 2adb2ca | 2016-06-29 01:44:35 | [diff] [blame] | 69 | MockUpdateClient() {} |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 70 | MOCK_METHOD1(AddObserver, void(Observer* observer)); |
| 71 | MOCK_METHOD1(RemoveObserver, void(Observer* observer)); |
| 72 | MOCK_METHOD3(Install, |
| 73 | void(const std::string& id, |
| 74 | const CrxDataCallback& crx_data_callback, |
sorin | 842703b | 2016-11-02 23:59:23 | [diff] [blame] | 75 | const Callback& callback)); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 76 | MOCK_METHOD3(Update, |
| 77 | void(const std::vector<std::string>& ids, |
| 78 | const CrxDataCallback& crx_data_callback, |
sorin | 842703b | 2016-11-02 23:59:23 | [diff] [blame] | 79 | const Callback& callback)); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 80 | MOCK_CONST_METHOD2(GetCrxUpdateState, |
| 81 | bool(const std::string& id, CrxUpdateItem* update_item)); |
| 82 | MOCK_CONST_METHOD1(IsUpdating, bool(const std::string& id)); |
| 83 | MOCK_METHOD0(Stop, void()); |
sorin | 8037ac8c | 2017-04-19 16:28:00 | [diff] [blame] | 84 | MOCK_METHOD4(SendUninstallPing, |
| 85 | void(const std::string& id, |
| 86 | const base::Version& version, |
| 87 | int reason, |
| 88 | const Callback& callback)); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 89 | |
| 90 | private: |
sorin | 2adb2ca | 2016-06-29 01:44:35 | [diff] [blame] | 91 | ~MockUpdateClient() override {} |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 92 | }; |
| 93 | |
| 94 | class FakeInstallerTraits : public ComponentInstallerTraits { |
| 95 | public: |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 96 | FakeInstallerTraits() {} |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 97 | ~FakeInstallerTraits() override {} |
| 98 | |
| 99 | bool VerifyInstallation(const base::DictionaryValue& manifest, |
| 100 | const base::FilePath& dir) const override { |
| 101 | return true; |
| 102 | } |
| 103 | |
sorin | 3574ef9 | 2016-08-03 16:46:01 | [diff] [blame] | 104 | bool SupportsGroupPolicyEnabledComponentUpdates() const override { |
sorin | 098a4a7 | 2016-08-30 04:43:24 | [diff] [blame] | 105 | return true; |
sorin | 3574ef9 | 2016-08-03 16:46:01 | [diff] [blame] | 106 | } |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 107 | |
| 108 | bool RequiresNetworkEncryption() const override { return true; } |
| 109 | |
sorin | 2892f721 | 2016-11-07 18:59:43 | [diff] [blame] | 110 | update_client::CrxInstaller::Result OnCustomInstall( |
| 111 | const base::DictionaryValue& manifest, |
| 112 | const base::FilePath& install_dir) override { |
| 113 | return update_client::CrxInstaller::Result(0); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 114 | } |
| 115 | |
| 116 | void ComponentReady( |
| 117 | const base::Version& version, |
| 118 | const base::FilePath& install_dir, |
| 119 | std::unique_ptr<base::DictionaryValue> manifest) override {} |
| 120 | |
| 121 | base::FilePath GetRelativeInstallDir() const override { |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 122 | return base::FilePath(relative_install_dir); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 123 | } |
| 124 | |
| 125 | void GetHash(std::vector<uint8_t>* hash) const override { GetPkHash(hash); } |
| 126 | |
| 127 | std::string GetName() const override { return "fake name"; } |
| 128 | |
sorin | 2adb2ca | 2016-06-29 01:44:35 | [diff] [blame] | 129 | update_client::InstallerAttributes GetInstallerAttributes() const override { |
| 130 | update_client::InstallerAttributes installer_attributes; |
| 131 | installer_attributes["ap"] = "fake-ap"; |
| 132 | installer_attributes["is-enterprise"] = "1"; |
| 133 | return installer_attributes; |
| 134 | } |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 135 | |
waffles | 77255cc | 2016-08-02 17:25:12 | [diff] [blame] | 136 | std::vector<std::string> GetMimeTypes() const override { |
| 137 | return std::vector<std::string>(); |
| 138 | } |
| 139 | |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 140 | private: |
| 141 | static void GetPkHash(std::vector<uint8_t>* hash) { |
| 142 | hash->assign(std::begin(kSha256Hash), std::end(kSha256Hash)); |
| 143 | } |
| 144 | }; |
| 145 | |
| 146 | class DefaultComponentInstallerTest : public testing::Test { |
| 147 | public: |
| 148 | DefaultComponentInstallerTest(); |
| 149 | ~DefaultComponentInstallerTest() override; |
| 150 | |
| 151 | MockUpdateClient& update_client() { return *update_client_; } |
| 152 | ComponentUpdateService* component_updater() { |
| 153 | return component_updater_.get(); |
| 154 | } |
| 155 | scoped_refptr<TestConfigurator> configurator() const { return config_; } |
| 156 | base::Closure quit_closure() const { return quit_closure_; } |
| 157 | |
| 158 | protected: |
| 159 | void RunThreads(); |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 160 | void Unpack(const base::FilePath& crx_path); |
| 161 | ComponentUnpacker::Result result() const { return result_; } |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 162 | |
| 163 | private: |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 164 | void UnpackComplete(const ComponentUnpacker::Result& result); |
| 165 | |
fdoray | b26583c | 2017-05-16 18:47:01 | [diff] [blame] | 166 | base::test::ScopedTaskEnvironment scoped_task_environment_; |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 167 | const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_ = |
| 168 | base::ThreadTaskRunnerHandle::Get(); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 169 | base::RunLoop runloop_; |
| 170 | base::Closure quit_closure_; |
| 171 | |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 172 | scoped_refptr<TestConfigurator> config_; |
| 173 | scoped_refptr<MockUpdateClient> update_client_; |
| 174 | std::unique_ptr<ComponentUpdateService> component_updater_; |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 175 | ComponentUnpacker::Result result_; |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 176 | }; |
| 177 | |
| 178 | DefaultComponentInstallerTest::DefaultComponentInstallerTest() |
fdoray | b26583c | 2017-05-16 18:47:01 | [diff] [blame] | 179 | : scoped_task_environment_( |
| 180 | base::test::ScopedTaskEnvironment::MainThreadType::UI) { |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 181 | quit_closure_ = runloop_.QuitClosure(); |
| 182 | |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 183 | config_ = base::MakeRefCounted<TestConfigurator>( |
fdoray | b26583c | 2017-05-16 18:47:01 | [diff] [blame] | 184 | base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()}), |
| 185 | base::ThreadTaskRunnerHandle::Get()); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 186 | |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 187 | update_client_ = base::MakeRefCounted<MockUpdateClient>(); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 188 | EXPECT_CALL(update_client(), AddObserver(_)).Times(1); |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 189 | component_updater_ = |
| 190 | base::MakeUnique<CrxUpdateService>(config_, update_client_); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 191 | } |
| 192 | |
| 193 | DefaultComponentInstallerTest::~DefaultComponentInstallerTest() { |
| 194 | EXPECT_CALL(update_client(), RemoveObserver(_)).Times(1); |
| 195 | component_updater_.reset(); |
| 196 | } |
| 197 | |
| 198 | void DefaultComponentInstallerTest::RunThreads() { |
| 199 | runloop_.Run(); |
| 200 | } |
| 201 | |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 202 | void DefaultComponentInstallerTest::Unpack(const base::FilePath& crx_path) { |
| 203 | auto component_unpacker = base::MakeRefCounted<ComponentUnpacker>( |
| 204 | std::vector<uint8_t>(std::begin(kSha256Hash), std::end(kSha256Hash)), |
| 205 | crx_path, nullptr, nullptr, config_->GetSequencedTaskRunner()); |
| 206 | component_unpacker->Unpack(base::Bind( |
| 207 | &DefaultComponentInstallerTest::UnpackComplete, base::Unretained(this))); |
| 208 | RunThreads(); |
| 209 | } |
| 210 | |
| 211 | void DefaultComponentInstallerTest::UnpackComplete( |
| 212 | const ComponentUnpacker::Result& result) { |
| 213 | result_ = result; |
| 214 | |
| 215 | EXPECT_EQ(update_client::UnpackerError::kNone, result_.error); |
| 216 | EXPECT_EQ(0, result_.extended_error); |
| 217 | |
| 218 | main_thread_task_runner_->PostTask(FROM_HERE, quit_closure_); |
| 219 | } |
| 220 | |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 221 | } // namespace |
| 222 | |
| 223 | // Tests that the component metadata is propagated from the default |
| 224 | // component installer and its component traits, through the instance of the |
| 225 | // CrxComponent, to the component updater service. |
| 226 | TEST_F(DefaultComponentInstallerTest, RegisterComponent) { |
| 227 | class LoopHandler { |
| 228 | public: |
| 229 | LoopHandler(int max_cnt, const base::Closure& quit_closure) |
| 230 | : max_cnt_(max_cnt), quit_closure_(quit_closure) {} |
| 231 | |
| 232 | void OnUpdate(const std::vector<std::string>& ids, |
| 233 | const UpdateClient::CrxDataCallback& crx_data_callback, |
sorin | 842703b | 2016-11-02 23:59:23 | [diff] [blame] | 234 | const Callback& callback) { |
| 235 | callback.Run(update_client::Error::NONE); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 236 | static int cnt = 0; |
| 237 | ++cnt; |
| 238 | if (cnt >= max_cnt_) |
| 239 | quit_closure_.Run(); |
| 240 | } |
| 241 | |
| 242 | private: |
| 243 | const int max_cnt_; |
| 244 | base::Closure quit_closure_; |
| 245 | }; |
| 246 | |
| 247 | const std::string id("jebgalgnebhfojomionfpkfelancnnkf"); |
| 248 | |
| 249 | // Quit after one update check has been fired. |
| 250 | LoopHandler loop_handler(1, quit_closure()); |
| 251 | EXPECT_CALL(update_client(), Update(_, _, _)) |
| 252 | .WillRepeatedly(Invoke(&loop_handler, &LoopHandler::OnUpdate)); |
| 253 | |
| 254 | EXPECT_CALL(update_client(), GetCrxUpdateState(id, _)).Times(1); |
| 255 | EXPECT_CALL(update_client(), Stop()).Times(1); |
| 256 | |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 257 | auto installer = base::MakeRefCounted<DefaultComponentInstaller>( |
| 258 | base::MakeUnique<FakeInstallerTraits>()); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 259 | installer->Register(component_updater(), base::Closure()); |
| 260 | |
| 261 | RunThreads(); |
| 262 | |
| 263 | CrxUpdateItem item; |
| 264 | EXPECT_TRUE(component_updater()->GetComponentDetails(id, &item)); |
| 265 | const CrxComponent& component(item.component); |
sorin | 2adb2ca | 2016-06-29 01:44:35 | [diff] [blame] | 266 | |
| 267 | update_client::InstallerAttributes expected_attrs; |
| 268 | expected_attrs["ap"] = "fake-ap"; |
| 269 | expected_attrs["is-enterprise"] = "1"; |
| 270 | |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 271 | EXPECT_EQ( |
| 272 | std::vector<uint8_t>(std::begin(kSha256Hash), std::end(kSha256Hash)), |
| 273 | component.pk_hash); |
| 274 | EXPECT_EQ(base::Version("0.0.0.0"), component.version); |
| 275 | EXPECT_TRUE(component.fingerprint.empty()); |
| 276 | EXPECT_STREQ("fake name", component.name.c_str()); |
sorin | 2adb2ca | 2016-06-29 01:44:35 | [diff] [blame] | 277 | EXPECT_EQ(expected_attrs, component.installer_attributes); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 278 | EXPECT_TRUE(component.requires_network_encryption); |
sorin | 098a4a7 | 2016-08-30 04:43:24 | [diff] [blame] | 279 | EXPECT_TRUE(component.supports_group_policy_enable_component_updates); |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 280 | } |
| 281 | |
Sorin Jianu | 316232a | 2017-05-26 20:22:30 | [diff] [blame^] | 282 | // Tests that the unpack path is removed when the install succeeded. |
| 283 | TEST_F(DefaultComponentInstallerTest, UnpackPathInstallSuccess) { |
| 284 | auto installer = base::MakeRefCounted<DefaultComponentInstaller>( |
| 285 | base::MakeUnique<FakeInstallerTraits>()); |
| 286 | |
| 287 | Unpack(test_file("jebgalgnebhfojomionfpkfelancnnkf.crx")); |
| 288 | |
| 289 | const auto unpack_path = result().unpack_path; |
| 290 | EXPECT_TRUE(base::DirectoryExists(unpack_path)); |
| 291 | |
| 292 | const auto manifest = update_client::ReadManifest(unpack_path); |
| 293 | |
| 294 | base::ScopedPathOverride scoped_path_override(DIR_COMPONENT_USER); |
| 295 | base::FilePath base_dir; |
| 296 | EXPECT_TRUE(PathService::Get(DIR_COMPONENT_USER, &base_dir)); |
| 297 | base_dir = base_dir.Append(relative_install_dir); |
| 298 | EXPECT_TRUE(base::CreateDirectory(base_dir)); |
| 299 | const auto result = installer->Install(*manifest, unpack_path); |
| 300 | EXPECT_EQ(0, result.error); |
| 301 | EXPECT_FALSE(base::PathExists(unpack_path)); |
| 302 | |
| 303 | EXPECT_CALL(update_client(), Stop()).Times(1); |
| 304 | } |
| 305 | |
| 306 | // Tests that the unpack path is removed when the install failed. |
| 307 | TEST_F(DefaultComponentInstallerTest, UnpackPathInstallError) { |
| 308 | auto installer = base::MakeRefCounted<DefaultComponentInstaller>( |
| 309 | base::MakeUnique<FakeInstallerTraits>()); |
| 310 | |
| 311 | Unpack(test_file("jebgalgnebhfojomionfpkfelancnnkf.crx")); |
| 312 | |
| 313 | const auto unpack_path = result().unpack_path; |
| 314 | EXPECT_TRUE(base::DirectoryExists(unpack_path)); |
| 315 | |
| 316 | const auto manifest = update_client::ReadManifest(unpack_path); |
| 317 | |
| 318 | // Test the precondition that DIR_COMPONENT_USER is not registered with |
| 319 | // the path service. |
| 320 | base::FilePath base_dir; |
| 321 | EXPECT_FALSE(PathService::Get(DIR_COMPONENT_USER, &base_dir)); |
| 322 | |
| 323 | // Calling |Install| fails since DIR_COMPONENT_USER does not exist. |
| 324 | const auto result = installer->Install(*manifest, unpack_path); |
| 325 | EXPECT_EQ( |
| 326 | static_cast<int>(update_client::InstallError::NO_DIR_COMPONENT_USER), |
| 327 | result.error); |
| 328 | EXPECT_FALSE(base::PathExists(unpack_path)); |
| 329 | |
| 330 | EXPECT_CALL(update_client(), Stop()).Times(1); |
| 331 | } |
| 332 | |
sorin | 6a57db9 | 2016-06-27 22:28:15 | [diff] [blame] | 333 | } // namespace component_updater |